当前位置: 首页 > news >正文

指针的进阶

一、字符指针
int main()
{char ch = 'w';char* pc = &ch;//pc就是字符指针//const char *p = "abcdef";//这里其实是把字符串"abcdef"的首地址放入了指针p中//*p = 'w';//这是错误的无法修改值(可以看到这里绿色波浪线警告)char arr[] = "abcdef";//创建了一个arr数组,里面存了字符串char* p = arr;*p = 'w';//此时这里的首元素可以被修改printf("%s\n", arr);return 0;
}

可以发现,通过字符指针 p ,将数组arr首元素a改为了w

而创建指针,直接指向字符串,是没法修改的

因为p里面存储的是,字符串abcdef的首地址

 

仔细比较二者之间的区别

int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";//这里字符型数组 str1[]和str2[]是分别创建了两块不同的空间,来存放相同的hello bit.内容,//这两块空间的起始地址不同,所以str1和str2代表的首元素地址也不同。const char* str3 = "hello bit.";const char* str4 = "hello bit.";//这里是创建了两个符号型指针str3和str4指向了同一块空间hello bit.的首地址//所以指针str3和str4的地址相同if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");//str1 and str2 are not sameif (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");//str3and str4 are samereturn 0;
}
        这里str3str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当 几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化 不同的数组的时候就会开辟出不同的内存块。所以str1str2不同,str3str4不同。

 

 

二、 指针数组
int arr1[10]; //整形数组 - 存放整型的数组
char arr2[4]; //字符数组 -存放字符的数组
那么指针数组 - 存放指针的数组
char* arr3[5];//

1. 指针数组举例

int main()
{//指针数组:是一个存放指针的数组。char* arr[] = { "abcdef", "hehe", "qwer" };//数组里每一个元素,都是字符串的首地址,指针int i = 0;for (i = 0; i < 3; i++){printf("%s\n", arr[i]);//arr[i] <=> *(arr+i)}return 0;
}

通过指针数组里,每个元素(首元素地址 - 指针),循环打印出数组里完整的字符串

2.用一维数组模拟实现二维数组
int main()
{//创建3个一维数组int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };//arr[i] == *(arr+i)//创建arr指针数组 ,是一个存放整型指针的数组int* arr[] = { arr1, arr2, arr3 };//将3个一维数组的首元素地址(指针)存储int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){//printf("%d ", arr[i][j]);printf("%d ", *(arr[i]+j));//arr[i]遍历一维数组,arr[i][j]遍历得到一维数组里的每个元素,}printf("\n");}return 0;
}

 

 

三、数组指针
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。

//p1, p2分别是什么?

int *p1[10];  //指针数组

int (*p2)[10];//数组指针
如果容易混淆,可以试着这样分辨:
1)int *p1[10];  去掉命名为 int * [10] ,可以发现是一个拥有10个元素的数组,该数组里每个元素的类型是 int * 也就是整型指针类型 ,最后结合起来就是,指针数组
2)int (*p2)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合
简单来说,指针p先和谁结合,他就是什么东西
int *p1[10] 先和 [] 结合,则为数组(然后是int * ,类型为整型指针)则为指针数组
int (*p2)[10] 先和*结合 ,则为指针(然后是int [], 整型数组)则为数组指针
1.&数组名VS数组名

arr 和 &arr 有什么区别?

数组名绝大部分情况下是数组首元素的地址
但是有2个例外
1. sizeof(数组名) - sizeof内部单独放一个数组名的时候。

 sizeof(arr) - 此时数组名表示的整个数组,计算得到的是数组的总大小
2. &arr  - 这里的数组名表示整个数组,取出的是整个数组的地址,从地址值的角度来讲和数组首元素的地址是一样的,但是意义不一样

int main()
{int arr[10] = { 0 };printf("%d\n", sizeof(arr)); // 此时arr表示整个数组,计算得到整个数组的内存大小 - 40字节printf("%p\n", arr);//int * //arr数组名通常为首元素地址printf("%p\n", arr+1);//首元素地址 +1 ,指针往后移动一个int长度(4字节)printf("%p\n", &arr[0]);//int* //取首元素地址printf("%p\n", &arr[0]+1);//首元素地址 +1 ,指针往后移动一个int长度(4字节)printf("%p\n", &arr);//int(*)[10] //取数组地址 - 此时地址代表整个数组printf("%p\n", &arr+1);//数组地址+1,跳过整个数组(移动了40个字节)int (*p)[10] = &arr;//p是一个数组指针//int(*)[10]return 0;
}

之所以说 arr 和arr[0]是首元素地址,是因为+1之后它们都只跳过一个整型,4个字节

而&arr +1后跳过了整个数组,40个字节(看16进制末尾两个数字,68-40=28H,换算成十进制,2*16^1 + 8*16^0 = 32+8 =40 )

从而推断它们指针的类型为  :

arr 和 arr[0] - int* 整型指针(+1跳过一个整型)

&arr  - int (*)[]  数组指针 (+1跳过整个数组)


 2.数组指针的运用

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);int (* p)[10] = &arr;//数组指针 - 指向arr整个数组int i = 0;//p  --- &arr//*p --- *&arr//*p --- arr//虽然对,但是不推荐//for (i = 0; i < sz; i++)//{//	printf("%d ", (*p)[i]);//*p解引用数组指针,得到arr数组,再通过下标i得到arr数组的每个元素//}//虽然对,但是不推荐//for (i = 0; i < sz; i++)//{//	printf("%d ", *((*p) + i));//因为p - &arr , *p - arr,arr即首元素地址,arr[i] <=> *(arr+1)//	//所以,得到*((*p)+1)//}//使用指针来访问//int* p = arr;//for (i = 0; i < sz; i++)//{//	printf("%d ", *(p + i));//}//下标的形式访问数组//for (i = 0; i < sz; i++)//{//	printf("%d ", arr[i]);//}return 0;
}

 打印出数组的内容:

1.最简单的办法就是通过下标 和 指针

 

2.通过数组指针访问数组元素(不太推荐)

有点杀鸡用牛刀的感觉,没有必要。

上面说过,数组指针是一个指向数组的指针,+1就跳过整个数组。而我们仅仅是想要打印数组的每个元素而已,就用下标或者普通的整型指针就可以了。

但是也可以试试用数组指针

1)第一种写法:解引用 数组指针 得到数组,再通过下标访问元素

int(*p)[10] = &arr  , p的值为数组地址&arr,*p解引用一次,*(&arr)得到数组arr,

数组arr[]用下标再去访问每个元素,实现代码

 2)第二种写法:解引用 数组指针 得到数组,再通过普通整型指针访问元素

同样的

     p  --- &arr
    *p --- *&arr
    *p --- arr

*p得到首元素地址,(*p)+1 地址+1,移动一个整型,再用  *((*p)+1 ) 再对整个地址解引用,得到元素

 

四、数组传参和指针传参
1.一维数组传参 (形参:数组 或者 指针)
//一维数组传参,形参是数组
void print1(int arr[10], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}
//一维数组传参,形参是指针
void print2(int *arr, int sz)
{int i = 0;for (i = 0; i < sz; i++){//printf("%d ", arr[i]);printf("%d ", *(arr+i));}printf("\n");
}
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int sz = sizeof(arr) / sizeof(arr[0]);print1(arr, sz);printf("\n");print2(arr, sz);return 0;
}

 

2.二维数组传参(形参是 二维数组 或者 数组指针)

//二维数组传参,形参是 二维数组
void print1(int arr[3][5], int r, int c)
{int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
//二维数组传参,形参是 数组指针 - 指向第一个一维数组的指针
void print2(int(*arr)[5], int r, int c)
{int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){//printf("%d ", *(*(arr + i) + j));//arr[i]printf("%d ", arr[i][j]);//arr[i]}printf("\n");}
}
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);printf("\n");print2(arr, 3, 5);return 0;
}

1)二维数组传参,形参是 二维数组

       int arr[3][5]传上去的是首元素的地址,也就是第一个一维数组的地址。

 arr[i]得到每一个一维数组, arr [i][j]进而得到一维数组里的每个元素,这样,二维数组里每个元素便得到了 

2)二维数组传参,形参是 数组指针 - 指向第一个一维数组的指针

int(*arr)[5] 传上去的是数组指针,指向第一个一维数组的首元素地址,同理遍历得到每个元素的地址

 

五、 函数指针
函数指针:指向函数的指针
&函数名 得到函数的地址
1.函数指针
void test()
{
printf("hehe\n");
}
//下面pfun1pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参 数,返回值类型为void
int Add(int x, int y)
{return x + y;
}
int main()
{//printf("%p\n", &Add);//printf("%p\n", Add);//pf就是函数指针int (* pf)(int, int) = Add;//函数的地址要存起来,就得放在【函数指针变量】中int ret = (*pf)(3, 5);//int ret = Add(3, 5);//int ret = pf(3, 5);printf("%d\n", ret);//&函数名得到就是函数的地址printf("%p\n", Add);printf("%p\n", &Add);return 0;
}

 

2.可以给函数指针重命名

typedef int* ptr_t;
typedef void(*pf_t)(int);//将void(*)(int)类型重新起个别名叫pf_t

typedef void(*pf_t2)(int);//pf_t2是类型名
void(*pf)(int);//pf是函数指针变量的名字
 

3.两段有趣的代码(眼前一黑)
//代码1
( *void ()( ) )0 )( );
该代码是一次函数调用
1. 将0强制类型转换void (*)() 类型的函数指针
2. 这就意味着0地址处放着一个函数函数没参数,返回类型是void
3. 调用0地址处的这个函数
 
//代码2
void (*signal(int , void (*)(int) ) )( int );
gai代码是一个函数的声明
    函数的名字是signal
    signal函数的参数第一个是int类型,第二个是void(*)(int)类型的函数指针
    该函数指针指向的函数参数是int,返回类型是void
     
    signal函数的返回类型也是一个函数指针
    该函数指针指向的函数参数是int,返回类型是void
    
    void (* signal(int, void(*)(int)))(int)
6. 函数指针数组
7. 指向函数指针数组的指针
8. 回调函数
9. 指针和数组面试题的解析
http://www.lryc.cn/news/18511.html

相关文章:

  • 一元二次方程方程的类
  • Ask林曦|来回答,30个你关心的日常问题(二)
  • 哪款电容笔适合开学季?电容笔和Apple Pencil的区别
  • Qt之Qprocess
  • 为什么不愿意专升本 学历有什么用
  • 构造函数的使用大全
  • ASP.NET Core MVC 项目 IOC容器
  • ARM工控机/网关- 钡铼技术
  • 为什么都在喊数据可视化?它究竟怎么做?
  • nodejs+vue停车场停车位短租系统vscode
  • 物理真机上LUKS结合TPM的测试 —— 使用随机数密钥
  • Linux USB 开发指南
  • FreeRTOS入门(03):队列、信号量、互斥量
  • Biome-BGC在模拟过程中,如何使用Linux、Python等,完成前处理和后处理工作???
  • 【unittest学习】unittest框架主要功能
  • 京东测开岗3+1面经+经验分享,拿到offer,月薪34k....
  • 后端接收格式为x-www-form-urlencoded的数据
  • LeetCode 707. 设计链表
  • HTTP的主要作用是什么
  • SpringBoot系列-- @Enable 模块驱动
  • PHP程序员适合创业吗?
  • 2023年CDGA考试-第12章-元数据(含答案)
  • 数据结构之顺序表篇
  • ZBC通证月内已翻倍,Nautilus Chain 上线前夕的“开门红”
  • 人工智能练习题:激活函数需要满足的条件、提高CNN的泛化能力、CNN输出特征图大小计算
  • KingbaseES Json 系列三:Json数据操作函数一
  • 《设计模式》单例模式
  • C/C++每日一练(20230224)
  • 基于YOLO的酸枣病虫害检测识别实践
  • WAF:ModSecurity on Nginx(15)