IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 一篇文章搞定C语言指针,指针超详细讲解,及指针面试题 -> 正文阅读

[C++知识库]一篇文章搞定C语言指针,指针超详细讲解,及指针面试题

1、 字符指针

常见的字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char* ;一般使用为:

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0;
}

不可修改的字符指针

int main()
{
    char* pstr ="hello";
    //一定切记不是把hello放到pstr里面去
    printf("%s\n", pstr);
    //直接使用地址打印字符串,不用解引用
    printf("%c",*pstr);//打印h
    //证明里面放的是首元素的地址
    
    //常量字符串不允许被修改,用const加以修饰
    const char* pstr ="hello";
    return 0;
}

代码 char* pstr = “hello”; 特别容易让同学以为是把字符串 hello bit 放到字符指针 pstr 里了,但是/本质是把字符串 hello 首字符的地址放到了pstr中。
字符指针的例题:

#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    char *str3 = "hello bit.";
    char *str4 = "hello bit.";
    if(str1 ==str2)
 printf("str1 and str2 are same\n");
    else
 printf("str1 and str2 are not same\n");
       
    if(str3 ==str4)
 printf("str3 and str4 are same\n");
    else
 printf("str3 and str4 are not same\n");
       
    return 0;
}
//str1不等于str2,str3等于str4

解析:str1和str2是两个独立的数组,开辟了两块独立的空间,所以str1和str2地址不同,str3和str4都指向同一个常量字符串,因为常量字符串不能被修改,又str3和str4都保存的字符串的首地址,即都指向同一位置。

图片解析:

在这里插入图片描述

2、指针数组

int arr[10];//整型数组-存放整型的数组
char s[10];//字符数组-存放字符的数组
//指针数组-存放指针的数组
int *p[10];//存放int *的数组
char*p[10];//存放char*的数组

int main()
{
    int arr1[]={1,3,4,5};
    int arr2[]={2,6,5,7};
    int arr3[]={1,9,6,4};
    int *p[]={arr1,arr2,arr3};
    int i=0;
    for(i=0;i<3;i++)
    {
        int j=0;
        for(j=0;j<5;j++)
        {
            printf("%d",p[i][j]);
        //抽象理解为二维数组,依靠p[i][j]==*(p[i]+j)
        }
        printf("\n");
    }
}

图片解析:

在这里插入图片描述
常量字符串数组

int main()
{
    const char *arr[3]={"asdc","bds","cbsd"};
    //将a,b,c等首元素的地址存入arr
    int i=0;
    for(i=0;i<3;i++)
    {
        printf("%s\n",arr[i]);
    }
    return 0;
}

补充:关于printf打印字符串,只要提供起始地址就能进行打印,但是只针对打印字符串。另外指针数组的数组名是一级指针,要用二级指针存储。

图片解析:

在这里插入图片描述

3、数组指针

数组指针是指针,一种指向数组的指针。

int main()
{
    int arr[10];
    int *p=arr;//取出数组首元素的地址,放到整型指针中
    int (*parr)[10]=&arr;//取出的是数组的地址,应该存放到数组指针中
    //int (*)[10]为指针parr的类型
    //首先parr为数组,指向大小为10的数组,数组的的元素为int类型
    
    int *p1[10];
    int (*p2)[10];
    //p1, p2分别是什么?
    p1为指针数组,p2为数组指针
    
    int (*p)[10];
}

解释:p先和*结合,说明p是一个指针变量,然后接着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。

这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合。

使用数组指针的一种遍历
这种写法是为了让大家加强对指针的理解,在实际的操作过程中,这样写只会加强代码的复杂性。

void Print(int(*parr)[10],int sz)
{
    int i=0;
    for(i=0;i<sz;i++)
    {
        printf("%d",parr[0][i]);
        //[0]找到了这个数组的第一行,[i]来找一行的元素
        printf("%d",(*(parr+0))[i]);
        //将其看成一个只有一行的二维数组
        printf("%d",(*parr)[i]);
        //(*parr)相当于parr指向的数组的数组名
    }
}
int mian()
{
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    int sz=sizeof(arr)/sizeof(arr[0]);
    Print(&arr,sz);
    return 0;
}

在这里插入图片描述
对于数组指针,在使用的过程中一定要慎重,随意的使用会造成复杂的的升高和难以理解,如上述代码,下面来看一下数组指针的优点使用,推荐在二维数组及其以上使用。

参数部分也是二维数组
Print1(int arr[3][5],int r,int c)
{
    int i=0;
    for(i=0;i<r;i++)
    {
        int j=0;
        for(j=0;j<c;j++)
        {
            printf("%d",arr[i][j]);
        }
        printf("\n")
    }
}
void Print2(int (*p)[5],int r,int c)
{
    int i=0;
    for(i=0;i<r;i++)
    {
        int j=0;
        for(j=0;j<c;j++)
        {
            printf("%d",*(*(p+i)+j));
            printf("%d",p[i][j]);
            //两种方法都能实现打印,细细体会两种方法的实现
        }
    }
}
int main()
{
    int arr[3][5]={1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
    //二维数组传参
    Print1(arr,3,5);
    Print2(arr,3,5);//arr是数组名,数组名是首元素的地址
    //把二维数组的每一行看成一个元素,元素为一行的一维数组的数组
    //数组名是首元素的地址,二维数组的数组名是第一行的地址
    
}

分析一些数组和指针的问题:

int arr[5];
//arr和[]结合说明arr为数组,int说明为整型数组
int *parr1[10];
//parr1是一个数组,10个元素,每个元素是int*的
//所以parr1是一个存放指针的数组
int (*parr2)[10];
//parr2是一个数组指针,该指针指向的数组有10个元素,每个元素是int类型的
int (*parr3[10])[5];
int(*)[5]
//parr3是个数组,数组有10个元素
//每个元素是一个数组指针,该指针指向的数组有5个元素
//每个元素是int类型

在这里插入图片描述

4、数组参数、指针参数

我们在写程序的过程中,自定义函数的使用是必不可少的,既然有函数的使用,那么一定会有函数传参的过程,下面让我们来研究一下数组和指针在传参过程中的问题:
一维数组传参

#include <stdio.h>
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int arr[100])//ok 虽然语法正确,但是不建议
{}
void test(int *arr)//ok
{}
void test2(int *arr[20])//ok
{}
void test2(int *arr[])//ok
{}
void test2(int **arr)//ok 接收一级指针的地址,使用二级指针
{}
int main()
{
 int arr[10] = {0};
 int *arr2[20] = {0};
 //20个元素每个元素都是int*的,首元素的地址为一个一级指针的地址
 test(arr);
 test2(arr2);
}

解析:因为传参不会真实的创建数组,所以[]里的数值可以省略,数组也可为任意值,但是任意值的写法容易产生歧义,所以不建议。
二维数组传参

void test(int arr[3][5])//ok
{}
void test(int arr[][])//no
{}
void test(int arr[3][])//no
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//no
{}
void test(int* arr[5])//no
{}
void test(int (*arr)[5])//ok
{}
void test(int **arr)//no
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);//二维数组首元素的地址为第一行的地址
 
}

一级指针传参

#include <stdio.h>
void print(int *p, int sz)
{
 int i = 0;
 for(i=0; i<sz; i++)
 {
 printf("%d\n", *(p+i));
 }
}
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 //一级指针p,传给函数
 print(p, sz);
 return 0;
}

反向思维:当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

void test1(int *p)
{}
//test1函数能接收什么参数?
//1、整型变量的地址
//2、整型数组首元素的地址
//3、一级指针变量
void test2(char* p)
{}
//test2函数能接收什么参数?
//1、字符变量的地址
//2、字符数组首元素的地址
//3、一级指针的变量

二级指针传参
当函数的参数为二级指针的时候,可以接收什么参数?如下所示:

#include <stdio.h>
void test(int** ptr)
{
 printf("num = %d\n", **ptr); 
}
int main()
{
 int n = 10;
 int*p = &n;
 int **pp = &p;
 int *arr[5];
 test(pp);
 test(&p);
 tset(arr);
 return 0;
}

5、函数指针

先来看一段代码:

int Add(int x,int y)
{
    return x+y;
}
int main()
{
    printf("%p\n",&Add);
    printf("%p\n",Add);
    //两个一句打印的内容一样
    //&函数名和函数名意义相同

上述代码说明函数是有地址的,那么存放函数地址的指针称为函数指针。

int Add(int x,int y)
{
    return x+y;
}
int main()
{
    printf("%p\n",&Add);
    printf("%p\n",Add);
    int (*pf)(int,int)=&Add;
    int(*)(int,int)//函数指针的类型
    //pf是用来存放函数地址的-pf就是函数指针变量
    //pf与*结合说明pf是个指针,后面的括号说明函数的参数
    //为int,int类型的,前面的int说明返回值为int类型
    
    //利用函数指针调用函数
    ret=(*pf)(4,5);
    //或者ret=pf(4,5);
}

两段高阶代码:

//代码1
(*(void (*)())0)();
//代码是一次函数调用
//解析:
//1、代码中把0强制类型转换成为类型为void(*)()的一个函数的地址
//2、解引用0地址,就是去0地址处的这个函数,被调用的函数无参数,返回类型是void
//代码2
void (*signal(int , void(*)(int)))(int);
//这个代码是一次函数声明
//signal函数有两个参数,第一个是int类型,第二个是void(*)(int)的函数指针类型
//signal函数的返回值类型依然是:void(*)(int)的函数指针类型
//使用typedef简化
//typedef int s;s为int的别名,具有int的属性
//typedef void(*pf)(int);把void(*)(int)从新命名为pf,具有同等属性
//pf signal(int,pf);等价void (*signal(int , void(*)(int)))(int);

6、函数指针数组

函数指针数组-存放函数指针的数组

int main()
{
    int (*pf1)(int,int)=Add;
    int (*pf2)(int,int)=Sub;
    int (*pf3)(int,int)=Mul;
    int (*pf4)(int,int)=Div;
//对于同类型的函数指针单独存储,代码太过复杂,所以要使用函数指针数组
    int (*pfArr[4])(int,int)={Add,Sub,Mul,Div};
}

函数指针数组的用途:转移表

void menu()
{
    printf( "*************************\n");
    printf( " 1:Add            2:Sub \n" );
    printf( " 3:Mul            4:Div \n" );
    printf( "*************************\n");

}
int main()
{
        int x, y;
        int input = 1;
        int ret = 0;
 do
   {
       menu();
        scanf( "%d", &input);
        switch (input)
       {
        case 1:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = add(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 2:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = sub(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 3:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = mul(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 4:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = div(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 0:
                printf("退出程序\n");
              breark;
        default:
              printf( "选择错误\n" );
              break;
       }
 } while (input);
    
    return 0;
}
//上面的写法每一步都有相同的代码逻辑,代码冗余度高,下面来使用函数指针数组实现
int main()
{
    int x,y;
    int input=0;
    do
    {
        menu();
        printf("请选择>");
        scanf("%d",&input);
        //转移表-《c和指针》
        int (*pfArr[4])(int,int)={Add,Sub,Mul,Div};
        //下标和输入input对应起来   0    1   2   3
        if(input==0)
        {
            printf("退出计算器");
        }
        else if(input>=1&&input<=4)
        {
            printf("请输入两个操作数:>");
            scanf("%d %d",&x,&y);
            ret=pfArr[input](x,y);
            printf("%d\n",ret);
        }
    }while(input);
    return 0;
}
//除了上面的函数指针数组的方法外,还可以使用回调函数来解决
void Calc(int(*pf)(int,int))
{
    int x=0;
    int y=0;
    int ret=0;
    printf("请输入两个操作数>");
    scanf("%d %d",&x,&y);
    printf("%d",ret);
}
int main()
{
    int input;
    do
    {
        menu();
        printf("请选择");
        scanf("%d",&input);
          switch (input)
       {
        case 1:
            Calc(Add);
              break;
        case 2:
           Calc(Sub);
              break;
        case 3:
           Calc(Mul);
              break;
        case 4:
           Calc(Div) 
              break;
        case 0:
                printf("退出程序\n");
              breark;
        default:
              printf( "选择错误\n" );
              break;
    }while(input);
    return 0;
}
//通过参数传过去不同函数的地址,实现不同的功能,解决了代码写死的问题

7、指向函数指针数组的指针

指向函数指针数组的指针是一个指针指针指向一个数组,数组的元素都是 函数指针。

int arr;
int (*p)[10];=&arr;
//p是一个指向整型数组的指针
int *arr[10];//整型指针数组的地址
int* (*p)[10]=&arr;//整数指针数组的地址
//p是一个指向(整型指针数组)的指针
int Add(int x,int y)
{
    
}
int (*pf)(int,int)=Add;//pf是函数指针
int (*pfArr[5])(int,int);//pfArr是函数指针的数组
int (*(*ppfArr)[5])(int,int)ppfArr=&pfArr//ppfArr是一个指向函数指针数组的指针
//指向的那个数组有5个元素,每个元素为int(*)(int,int)类型

图片解析:
在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-22 14:29:00  更:2021-09-22 14:29:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:05:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码