C语言(指针)9
小羊在奋斗 2024-06-12 14:35:03 阅读 63
Hi~!这里是奋斗的小羊,很荣幸各位能阅读我的文章,诚请评论指点,关注+收藏,欢迎欢迎~~
💥个人主页:小羊在奋斗
💥所属专栏:C语言
本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为同样是初学者的学友展示一些我的学习过程及心得。文笔、排版拙劣,望见谅。
1、sizeof 和 strlen 的对比
1.1sizeof
1.2strlen
2、数组和指针试题解析
2.1一维数组
2.2字符数组
2.3二维数组
3、指针运算试题解析
1、sizeof 和 strlen 的对比
sizeof 和 strlen 我们已经很熟悉了,这里就不再做过多赘述,我们简单地做个对比就好。
1.1sizeof
(1)sizeof 是一个操作符,操作数是变量、表达式或者类型,计算的是变量所占内存空间的大小,单位是字节,返回值是 size_t 类型;
(2)sizeof 只关心占用内存空间的大小,不关心内存中存的什么数据;
(3)sizeof 后面的表达式是不真实参与运算的。
1.2strlen
(1)strlen 是一个库函数,求的是字符串或字符数组的长度,strlen 需要的是一个字符串或一个地址,统计的是字符串中\0之前的字符个数,返回值是 size_t 类型;
(2)使用 strlen 库函数需要包含头文件 <string.h>;
(3)strlen 不关心得到的地址是什么类型,一律转换为 char * 类型。
2、数组和指针试题解析
我们之前说过,数组名表示数组首元素的地址,但是有两个特例:sizeof(数组名)和 &数组名,这时候的数组名表示的是整个数组,sizeof(数组名)计算的是整个数组的大小,&数组名取出的是整个数组的地址。
2.1一维数组
#include <stdio.h>int main(){int a[] = { 1,2,3,4,5 }; //sizeof的返回值是size_t类型,用%zd打印printf("%zd\n", sizeof(a));//sizeof(数组名)计算的是整个数组的大小,结果为20printf("%zd\n", sizeof(a + 0));//数组名并没有单独作为sizeof的操作数,此时数组名为数组首元素的地址,sizeof(地址)的大小为4或8printf("%zd\n", sizeof(*a));//此时数组名表示数组首元素的地址,解引用得到的是数组首元素,结果为4printf("%zd\n", sizeof(a + 1));//此时数组名表示数组首元素的地址,+1跳过4个字节的大小,得到的是数组第二个元素的地址,结果为4或8printf("%zd\n", sizeof(a[1]));//a[0]得到的是数组首元素,数组元素为int型,结果为4printf("%zd\n", sizeof(&a));//&a得到的是整个数组的地址,只要是地址,sizeof(地址)的结果都为4或8printf("%zd\n", sizeof(&a + 1));//&a得到的是整个数组的地址,+1跳过20个字节的大小,得到的还是地址,结果为4或8printf("%zd\n", sizeof(&a[0]));//&a[0]得到的是数组首元素的地址,相当于数组名,结果是4或8printf("%zd\n", sizeof(&a[0] + 1));//&a[0] + 1得到的是数组第二个元素的地址,结果是4或8return 0;}
2.2字符数组
#include <stdio.h>int main(){char arr[] = { 'a', 'b', 'c', 'd', 'e' };printf("%zd\n", sizeof(arr));//此时的数组名表示整个数组,计算的是整个数组的大小,结果为5printf("%zd\n", sizeof(arr + 0));//此时的数组名表示的是数组首元素的地址,结果为4或8printf("%zd\n", sizeof(*arr));//此时的数组名表示的是数组首元素的地址,解引用得到了数组首元素,结果为1printf("%zd\n", sizeof(arr[1]));//arr[1]得到了数组首元素,结果为1printf("%zd\n", sizeof(&arr));//&数组名得到的是整个数组的地址,地址的大小为4或8printf("%zd\n", sizeof(&arr + 1));//对整个数组的地址+1跳过了5个字节的大小,得到的还是地址,结果为4或8printf("%zd\n", sizeof(&arr[0] + 1));//取出数组首元素的地址再+1得到的是数组第二个元素的地址,结果为4或8return 0;}
#include <stdio.h>#include <string.h>int main(){char arr[] = { 'a', 'b', 'c', 'd', 'e' };//数组中没有字符 '\0'printf("%zd\n", strlen(arr));//数组中没有字符 '\0',结果为随机值printf("%zd\n", strlen(arr + 0));//此时的数组名表示的是数组首元素的地址,结果还是随机值//printf("%zd\n", strlen(*arr));//此时的数组名表示数组首元素的地址,解引用得到了数组首元素字符'a',但strlen需要的是一//个字符串或地址,所以strlen会把字符'a'的ASCII码值97当做一个地址进行访问,程序会崩溃//printf("%zd\n", strlen(arr[1]));//arr[1]得到的是数组第二个数组字符'b',将98当做地址访问程序会崩溃printf("%zd\n", strlen(&arr));//&数组名得到的是整个数组的地址,还是从数组首元素的地址处开始访问,结果为随机值printf("%zd\n", strlen(&arr + 1));//整个数组的地址+1跳过5个字节的大小,访问后面的地址,结果还是随机值printf("%zd\n", strlen(&arr[0] + 1));//取出数组首元素的地址+1得到了数组第二个元素的地址,向后访问得到的结果还是随机值return 0;}
#include <stdio.h>#include <string.h>int main(){char arr[] = "abcde";//字符串末尾隐藏一个字符 '\0'printf("%zd\n", sizeof(arr));//此时的数组名表示的是整个数组,计算的是整个数组的大小,结果是6printf("%zd\n", sizeof(arr + 0));//此时的数组名表示的数组首元素的地址,计算的是首元素地址的大小,结果为4或8printf("%zd\n", sizeof(*arr));//此时的数组名表示的是数组首元素的地址,解引用得到的是数组首元素,大小为1printf("%zd\n", sizeof(arr[1]));//数组首元素的大小,结果为1printf("%zd\n", sizeof(&arr));//&数组名得到的是整个数组的地址,地址的大小为4或8printf("%zd\n", sizeof(&arr + 1));//整个数组的地址+1跳过6个字节的大小,得到还是地址,结果为4或8printf("%zd\n", sizeof(&arr[0] + 1));//取出数组首元素的地址+1得到的是数组第二个元素的地址,结果为4或8return 0;}
#include <stdio.h>#include <string.h>int main(){char arr[] = "abcde";//字符串末尾隐藏一个字符 '\0'printf("%zd\n", strlen(arr));//此时的数组名表示的是数组首元素的地址,从第一个字符开始数,结果是5printf("%zd\n", strlen(arr + 0));//此时的数组名表示的是数组首元素的地址,从第一个字符开始数,结果是5//printf("%zd\n", strlen(*arr));//对数组名解引用得到数组第一个元素字符'a',把'a'的ASCII码值当做地址访问,程序崩溃//printf("%zd\n", strlen(arr[1]));//得到数组第二个元素字符'b',程序崩溃printf("%zd\n", strlen(&arr));//得到整个数组的地址,还是从首元素开始访问,结果为5printf("%zd\n", strlen(&arr + 1));//整个数组的地址+1跳过6个字节的大小,指向了数组外面,结果是随机值printf("%zd\n", strlen(&arr[0] + 1));//数组首元素的地址+1指向第二个元素,往后开始访问结果为4return 0;}
#include <stdio.h>int main(){char* p = "abcde";//将字符'a'的地址存到指针变量p中printf("%zd\n", sizeof(p));//p中存的是字符'a'的地址,地址的大小是4或8printf("%zd\n", sizeof(p + 1));//p + 1指向的是字符'b'的地址,地址的大小是4或8printf("%zd\n", sizeof(*p));//p中存着字符'a'的地址,解引用得到字符'a',大小是1printf("%zd\n", sizeof(p[0]));//p[0] == *(p + 0),所以解引用得到的还是字符'a',大小是1printf("%zd\n", sizeof(&p));//p是一个字符指针变量,取出p的地址,地址的大小是4或8printf("%zd\n", sizeof(&p + 1));//p是一个字符指针变量,取出p的地址+1跳过4或8个字节,得到的还是地址,大小是4或8printf("%zd\n", sizeof(&p[0] + 1));//p[0]得到字符'a',取出字符'a'的地址+1指向了字符'b'的地址,大小是4或8return 0;}
#include <stdio.h>#include <string.h>int main(){char* p = "abcde";//将字符'a'的地址存到指针变量p中printf("%zd\n", strlen(p));//p中存的是字符'a'的地址,结果是5printf("%zd\n", strlen(p + 1));//p+1指向了字符'b'的地址,结果是4//printf("%zd\n", strlen(*p));//对p解引用得到了字符'a',strlen会把'a'的ASCII码值97当做一个地址去访问,产生错误//printf("%zd\n", strlen(p[0]));//p[0]得到的还是字符'a',产生错误printf("%zd\n", strlen(&p));//取出字符指针变量p的地址,strlen找字符'\0',产生随机值printf("%zd\n", strlen(&p + 1));//p的地址+1跳过4或8个字节,结果还是随机值printf("%zd\n", strlen(&p[0] + 1));//字符'a'的地址+1指向字符'b',结果是4return 0;}
2.3二维数组
#include <stdio.h>int main(){int a[3][4] = { 0 };printf("%zd\n", sizeof(a));//此时数组名表示整个数组,大小为48printf("%zd\n", sizeof(a[0][0]));//a[0][0]得到的是第1行的第1个元素,大小为4printf("%zd\n", sizeof(a[0]));//a[0]是二维数组第一行的数组名,此时数组名表达整个数组,大小为16printf("%zd\n", sizeof(a[0] + 1));//此时的数组名表示二维数组第一行第一个元素的地址,+1指向了第一行第二个元素,地址的大小为4或8printf("%zd\n", sizeof(*(a[0] + 1)));//a[0] + 1指向二维数组第一行第二个元素,解引用得到的是第一行第二个元素,大小为4printf("%zd\n", sizeof(a + 1));//此时的数组名表示二维数组首元素的地址,+1指向了二维数组的第二行,地址的大小为4或8printf("%zd\n", sizeof(*(a + 1)));//a + 1指向了二维数组的第二行,表示的是整个第二行的地址,解引用得到整个第二行的元素,大小为16printf("%zd\n", sizeof(&a[0] + 1));//a[0]是二维数组第一行的数组名,&数组名得到的是整个第一行的地址,+1得到的是二维数组第二行的地址,大小为4或8printf("%zd\n", sizeof(*(&a[0] + 1)));//对二维数组第二行的地址解引用得到的是整个第二行,大小为16printf("%zd\n", sizeof(*a));//此时的数组名表示的是二维数组首元素的地址,也就是第一行的地址,解引用得到第一行的所有元素,大小为16printf("%zd\n", sizeof(a[3]));//编译器将a[3]看作二维数组的第四行,第四行的数组名,此时的数组名表示的是整个数组,大小为16return 0;}
可能有小伙伴会说a[3]越界了,其实不是的。还记得我们开头对sizeof的介绍吗?sizeof后面的表达式不会真实参与计算,所以不存在越界。 我们也可以简单证明一下:
3、指针运算试题
(1):
创建一个一维整型数组,大小为5,&数组名取出整个数组的地址再+1跳过20个字节的大小指向了数组外,此时的地址(指针)类型为 int (*)[5]类型,强转为 int * 类型赋给整形指针变量pa。printf函数里的a表示数组首元素的地址,+1指向了数组第二个元素,再解引用就得到了数组第二个元素2;整型指针变量pa此时指向的是数组外,-1往后退4个字节的大小指向了数组第五个元素5,再解引用记得到了5。
(2):
在x86的环境下,结构体的大小是20个字节,创建了一个结构体类型指针变量p,将16进制数100000强转为结构体指针类型再赋给p。
在main函数中的p是一个结构体指针变量,指针p+1跳过20个字节,所以p的值应该+20,但是地址是16进制数,所以转换为16进制后p+1的值为0x100014;在第二个printf函数中将结构体指针p强转为unsigned long类型的无符号长整型,此时p的值不再是地址,+1就是整数+1,结果为0x100001;再将结构体指针p强转为unsigned int *类型的无符号整型指针,+1跳过4个字节,结果为0x100004。
要把指针+-整数和整数+-整数区分开来,指针+-整数跟指针的类型有关,整数+-整数就是+-整数。
(3):
创建了一个3行2列的二维数组并初始化了一些值,a[0]是二维数组第一行的数组名,表示的是第一行首元素的地址,赋给整型指针变量p,p[0]将相当于a[0][0],是二维数组第一行第一个元素。但是要注意上面二维数组的初始化,一般二维数组的初始化是花括号里面包含花括号或没有括号,但上面的初始化中(0, 1)是一个逗号表达式,从左到右依次计算结果是最后一个表达式的值,所以上面二维数组初始化后应该是这样的:
(4):
创建了一个5行5列的二维整形数组和类型为int (*)[4]的数组指针,将二维数组第一行的地址赋给数组指针变量p,但是a是数组名,作为二维数组首元素的地址它的类型是int (*)[5],所以p接收a的值时只能以它最大能接收的范围接收,简单地画个图理解:
当我们取出p[4][2]的地址和a[4][2]的地址再相减,通过前面的学习我们知道指针相减的绝对值得到的是指针之间的元素个数,通过上面的图我们可以很清楚的得到&p[4][2] - &a[4][2]的值是-4。当我们用%p和%d分别打印时,printf函数看到了%p就以为你想要打印地址,所以它就把-4当作一个地址来打印。而我们知道整数在内存中存的是补码,-4的补码是11111111111111111111111111111100,但地址通常是以16进制表示,所以再把-4的补码转化为16进制就是FFFFFFFC。而%d就是打印有符号整型的,所以就直接打印出-4。
(5):
创建一个2行5列的二维整形数组,将二维数组的地址取出+1,因为取出的是整个二维数组的地址所以+1跳过40个字节的大小,此时指针(地址)的类型是int (*)[2][5],所以还需要强转为int *类型的指针再赋给p1,p1 - 1向后走4个字节的大小指向了整数10。*(a+1)中的a表示的是二维数组首元素的地址,也就是二维数组第一行的地址,+1指向了第二行,再解引用就得到了第二行的数组名也就是首元素的地址,-1后指向了第一行的最后一个元素5。
(6):
我们之前学过char *p = “abcde”;的意思是将常量字符串“abcde”的首字符‘a’的地址存到p中,所以上面代码的意思是创建一个char *类型的一维数组a,将常量字符串“work”、“at”、“alibaba”等首字符的地址存到数组a中,再将数组首元素的地址赋给char **类型的指针变量pa中,pa++指向数组第二个元素,解引用就得到了数组内第二个字符串“at”首字符‘a’的地址,用%s打印就能打印出字符串“at”。
(7):
上面代码在内存中的存储大致可以用下图表示:
在解题之前我们先复习一下旧知识,自增(++)和自减(--)运算符会改变值本身,也就是说对于指针p而言,虽然p++和p+1得到的结果是一样的,但是p++后p的指向就固定了,而p+1后p的指向不会改变。
首先来看第一个,++pcc使指针pcc指向了数组pc的第二个元素,解引用得到了第二个元素c+2,c+2也是一个指针,指向的是数组c的第三个元素,再解引用就得到了数组c的第三个元素也就是字符串“point”的首字符‘p’的地址,用%s打印就得到了字符串“point”。
再来看第二个,首先算++pcc,因为之前pcc已经指向数组pc的第二个元素,所以这次++pcc使指针pcc指向了数组pc的第三个元素,解引用得到了第三个元素c+1,c+1再--变为c,c是数组名是其数组首元素的地址,解引用就得到了数组c的第一个元素也就是字符串“enter”首字符‘e’的地址,‘e’的地址(指针)+3得到了第二个‘e’的地址,用%s打印出“er”。
来看第三个,我们可以将上面的代码转换成:*(*(pcc - 2)) + 3,便于我们理解。pcc经过之前的两个自增运算已经指向了数组pc的第三个元素,此时-2指针pcc又重新指向了第一个元素,解引用得到c+3,c+3指向的是数组c的第四个元素,解引用得到字符串“first”首字符‘f’的地址,最后+3得到的是字符‘s’的地址,用%s打印得到“st”。
最后看第四个,同样的将上面的代码转化一下变为:*(*(pcc - 1) - 1) + 1,pcc此时指向的是数组pc的第三个元素,-1后指向了第二个元素,解引用得到c+2,c+2再-1得到c+1,c+1指向的是数组c的第二个元素,解引用得到字符串“new”首字符‘n’的地址,最后+1得到字符‘e’的地址,用%s打印得到“ew”。
总结:(1)要时刻注意指针变量运算和其他变量运算的区别,指针类型很关键;
(2)要清楚内存中的存储情况,心里要明白此时指针指向的是何处;
(3)要避免指针越界的情况发生;
(4)要清楚一个内存单元中存的是什么,其类型又是什么。
如果觉得我的文章还不错,请点赞、收藏 + 关注支持一下,我会持续更新更好的文章。
点击跳转下一节 —> C语言(字符、字符串函数)1
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。