C语言学习笔记---指针(7)---指针完结篇

目录

接上一节数组和指针笔试题解析

字符串数组

二维数组

指针运算笔试题解析

题⽬1

题⽬2

题⽬3

顺便复习一下逗号表达式:

题⽬4

题⽬5

题⽬6

题⽬7

总结


接上一节数组和指针笔试题解析

再再再三强调一下:
数组名是首元素的地址,但是有两个例外:
1.sizeof(数组名)计算的是整个数组的大小
注意:只有当sizeof()括号内放的是单独一个数组名才是表示整个数组,如果不是单独的数组名则表示首元素的地址,
比如sizeof(arr + 0)),那这里的arr表示的是首元素地址

2.&(数组名)取出来的是整个数组的大小

字符串数组

int main()
{
	//字符串数组---用字符串初始化
	char arr[] = "abcdef";//这个数组是7个元素,原型是"abcdef\0",第七个是\0
	printf("%zd\n", sizeof(arr));//7
	printf("%zd\n", sizeof(arr + 0));//4或者8,arr+0是首元素的地址,地址的大小是4或者8
	printf("%zd\n", sizeof(*arr));//1,计算的是数组首元素的大小
	printf("%zd\n", sizeof(arr[1]));//1,计算的是第二个元素的大小
	printf("%zd\n", sizeof(&arr));//4或者8,取出来的是数组的地址,是地址,大小就是4或者8
	printf("%zd\n", sizeof(&arr + 1));// 4或者8,跳过整个数组,指向了数组的后边的某个空间的地址
	printf("%zd\n", sizeof(&arr[0] + 1));//4或者8,计算的是第二元素的地址,是地址,大小就是4或者8
	return 0;
}

换成strlen计算又会如何呢?

#include//strlen()的头文件
//strlen()括号内要求被传的是个地址!!!!!否则程序崩溃
int main()
{
	char arr[] = "abcdef";//
	printf("%zd\n", strlen(arr));//6
	//arr在这里表示的是首元素的地址,那就从首元素开始计算,直到遇到\0终止,\0不计入内
	
	printf("%zd\n", strlen(arr + 0));//6,arr首元素的地址,+0等于没加,还是首元素的地址
	
	//printf("%zd\n", strlen(*arr));//?err
	//arr表示首元素的地址,解引用后找到首元素'a','a'的ASCII码值是97,streln将97当成地址去寻找对应的元素,程序崩溃

	//printf("%zd\n", strlen(arr[1]));//?err,传的是第二个元素,而不是地址,程序崩溃
	
	printf("%zd\n", strlen(&arr));//6
	//注:&(数组名)取出来的是整个数组的大小,&arr虽然是数组的地址,但是也是指向数组的起始位置,所以也从首元素开始计算
	//上一节我们讲过strlen的原型是size_t strlen(const char* str),
	//参数类型是一个char*,即一个存放了地址的指针,而&arr直接是个数组的地址,
	//所以传给strlen时和strlen要求的参数类型有所差异,所以编辑器会报个小小的警告
	//正确做法是将&arr取到的地址存放到一个指针里面再传过去,比如: char (*parr)[7]=&arr;而parr的类型是 char(*)[7]
	
	printf("%zd\n", strlen(&arr + 1));//随机值
	//数组的地址+1就跳过整个数组"abcdef\0",从\0后面开始计算,但是不知道后面是什么,得到的是一个随机值
	
	printf("%zd\n", strlen(&arr[0] + 1));//5,第二个元素的地址,从第二个元素开始数,结果是5

	return 0;
}

再看看这串代码

int main()
{
	char* p = "abcdef";
	//指针p指向字符串"abcdef\0",p存放了字符串的起始位置即首字符'a'的地址,p的类型是char*
	printf("%zd\n", sizeof(p));//4或者8,,计算的是p这个指针变量的大小,而指针的大小总是4或者8
	printf("%zd\n", sizeof(p + 1));//4或者8,p + 1是第二个字符的地址
	printf("%zd\n", sizeof(*p));//1,计算的是'a'的大小
	printf("%zd\n", sizeof(p[0]));//1,p[0]==*(p+0)计算的是'a'的大小
	printf("%zd\n", sizeof(&p));//4或者8,&p取出指针变量p的地址,是地址,大小就是4或者8
	printf("%zd\n", sizeof(&p + 1));//4或者8
	//此处需要复习一下二级指针:
	
	//先看一级指针
	//int a = 10;
	//int* p = &a;
	//p + 1;//p跳过一个int的大小

	//再看二级指针
	//char* p;
	//char** q = &p;
	//靠近q的*告诉我们q是个指针(即q里面存放了地址),而char*告诉我们q指向的对象是个char*型的指针
	//而q+1跳过一个char*的大小,即跳过一个char*型指针变量的大小,指向p指针后面的空间的某个地址,大小是4或者8
	//如果还是看不懂请看下图1
	
	
	printf("%zd\n", sizeof(&p[0] + 1));//4或者8,是字符'b'的地址


	return 0;
}

【图1】

C语言学习笔记---指针(7)---指针完结篇_第1张图片

一个小小的感悟:指针变量名和数组名一般存放的都是首元素的地址
//指针变量名[ ]==数组名[ ]

int main()
{
	char* p = "abcdef";//原型"abcdef\0"
	printf("%d\n", strlen(p));//6,p存放的是字符串的首字符的地址,从首字符开始向后数
	printf("%d\n", strlen(p + 1));//5,从'b'开始数
	printf("%d\n", strlen(*p));//传的不是地址,程序崩溃
	printf("%d\n", strlen(p[0])); //传的不是地址,程序崩溃
	
	printf("%d\n", strlen(&p));//随机值
	//取出来的是指针p的地址,从p的地址开始数,后面不知道是什么,
	//不知道什么时候遇到\0,以结果是随机值,可以看下图2想象一下,
	//注意:p的地址和字符串的地址是两个不同的空间。
	printf("%d\n", strlen(&p + 1));//随机值
	//跳过p的地址向后数,后面是什么就更不知道了,可以看下图3想象一下,
	
	printf("%d\n", strlen(&p[0] + 1));//5,从'b'开始向后数
	
	return 0;
}

【图2】

C语言学习笔记---指针(7)---指针完结篇_第2张图片

【图3】

C语言学习笔记---指针(7)---指针完结篇_第3张图片

模仿strlen()函数计算字符数组里面的字符个数

size_t my_strlen(const char*str)
{
	int count = 0;
	while (*str)//只要不是\0就继续循环,遇到\0就跳出循环
	//'\0'是空字符,对应的ASCII码值是0,while(0),0表示假,因此跳出循环
	{
		count++;
		str++;
	}
	return count;
}

int main()
{
	char str[7] = "abcdef";
	size_t count=my_strlen(str);
	printf("%zd", count);
	return 0;
}

二维数组

int main()
{
	//注意:二维数组也是数组,之前对数组名理解也是适合的
	int a[3][4] = { 0 };
	printf("%zd\n", sizeof(a));//12*=48,a是二维数组的数组名,计算的是整个数组
	printf("%zd\n", sizeof(a[0][0]));//4,第一行第一列的元素
	printf("%zd\n", sizeof(a[0]));//16,计算的是整个第一行的大小
	//a[0]是第一行一维数组的数组名,数组名单独放在sizeof的括号内,表示的是计算整个第一行的一维数组的大小

	printf("%zd\n", sizeof(a[0] + 1));//4或者8
	//注意:这个a[0]不是单独放在sizeof的括号内,因此它在这里不表示整个第一行的一维数组
	//在这里a[0]表示的是第一行的一维数组首元素的地址,+1表示第一行的第二个元素,即a[0][1]的地址
	//是地址,大小就是4或者8

	printf("%zd\n", sizeof(*(a[0] + 1)));//4,a[0][1]
	printf("%zd\n", sizeof(a + 1));//4或者8
	//在这里a不单独放在括号内,表示的是首元素的地址
	//注意:对于二维数组来说,首元素就是第一行,即第一行一维数组的地址,+1即a[1]第二行一维数组的地址
	printf("%zd\n", sizeof(*(a + 1)));//16
	//*(a + 1))==a[0],第二行的数组名单独放在sizeof内部,计算的是第二行的大小
	printf("%zd\n", sizeof(&a[0] + 1));//4或者8,&a[0]取的是整个第一行的地址,+1就取到了第二行的地址
	printf("%zd\n", sizeof(*(&a[0] + 1)));//16,第二行的一维数组的大小
	//int(*p)[4]==&a[0]+1;
	//对这样一个数组指针解引用拿到的是这个数组,数组里面有4个int元素
	printf("%zd\n", sizeof(*a));//16,这里a是第一行的地址,*a第一行的数组
	//*a==*(a+0)==a[0]
	printf("%zd\n", sizeof(a[3]));//16
	//没有第四行,但是这个不存在越界,因为sizeof内部的表达式不会真实计算的
	//sizeof只要知道类型就能计算大小
	//如果这个数组有第四行的话,那a[3]其实跟a[0],a[1],a[2]一个道理,也就是说a[3]存在的话,类型也是int
	//计算的是第四行的大小,但是它不会真的去访问第四行,只是根据类型来确定
	return 0;
}

strlen只计算字符,不计算整型!

以上最重要的是理解数组名表示的意义
数组名的意义:
1. sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。
2. & 数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表⽰⾸元素的地址。

指针运算笔试题解析

题⽬1

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);//&a取得是整个数组的地址
	//(int*)强制转换成类型指针
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}

程序的结果是什么?
打印结果为2,5(图解见图4)

【图4】

C语言学习笔记---指针(7)---指针完结篇_第4张图片

题⽬2

在X86环境下
假设结构体的⼤⼩是20个字节
程序输出的结果是啥?

struct Test//结构体标签名
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;//p是结构体变量名,后面是初始化内容

如果没有 (struct Test*)时,0x100000是一个整型,强制类型转换成(struct Test*)才能赋值给一个指针变量
因为p的类型就是一个struct Test*

补充一点:以上结构体的类型是以下这一整坨,包括最后那个*号

struct Test
{
    int Num;
    char* pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*

int main()
{
	printf("%p\n", p + 0x1);//0x1 十六进制的1就是1,
	//p是结构体指针,结构体指针+1就跳过一个结构体大小
	//p+0x1则表示0x100000+20
	//16进制逢16进1,20进1还剩下4,则0x100000+20==0x100014  
	printf("%p\n", (unsigned long)p + 0x1);
	//将p强制类型转换成整数(注意:这里转换成的不是指针!),整数+1就是加1,
	//0x100000+1=0x100001
	printf("%p\n", (unsigned int*)p + 0x1);
	//将p强制类型转换成整型指针,整型指针+1就跳过1个int类型大小
	//0x100000+4=0x100004
	
	//注意:因为上面是用%p打印的,地址打印时,在X86环境下是打印8位的

	printf("%x\n", p + 0x1);//如果改成用%x打印的话,打印的结果将省略前面的2个零
	printf("%#x\n", p + 0x1);//如果想要打印0x的话,可以写成%#x

	return 0;
}

补充:每个十六进制数为一个半字节,两个十六进制数合在一起为一个字节。
1个字节是8位,二进制8位:xxxxxxxx 范围从00000000-11111111,表示0到255。
一位16进制数(用二进制表示是xxxx)最多只表示到15(即对应16进制的F),
要表示到255, 就还需要第二位。
所以1个字节=2个16进制字符,一个16进制位=0.5个字节。

题⽬3

int main()
{
	//注意逗号表达式
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	//原本如果想要数组的第一行是0,1的话,应该是用大括号的:
	//int a[3][2] = { {0, 1}, {2, 3}, {4, 5} };
	//则这个数组将会是这样的:
	//第一行:0 1
	//第二行:2 3
	//第三行:4 5
	//但是这道题用的是小括号,里面则是一个逗号表达式
	//所以这个数组只有三个真正初始化的元素,其他的默认为0,应该是这样的:
	//第一行:1 3
	//第二行:5 0
	//第三行:0 0
	//不知道为什么的请往下翻看一下逗号表达式的复习!
	int* p;
	p = a[0];//a[0]表示第一行的一维数组的数组名,放的是这个一维数组的首元素的地址
	printf("%d", p[0]);//结果为1
	return 0;
}

顺便复习一下逗号表达式:


逗号表达式的要领:
(1) 逗号表达式的运算过程为:从左往右逐个计算表达式。
(2) 逗号表达式作为一个整体,它的值为最后一个表达式(也即表达式n)的值。
(3) 逗号运算符的优先级别在所有运算符中最低。

举个例子:

int main()
{
	int a1, a2, b = 2, c = 7, d = 5; // 第1行
	a1 = (++b, c--, d + 3); // 第2行,用()括起来表示先运行()里面的表达式,a1=2+1=3=b,a1=7 c=6, a1=5+3=8
	a2 = ++b, c--, d + 3; //第3行,没有括号,则赋值运算符优先于逗号运算符,a2=b=3+1=4,c=5,5+3=8
	
	//对于给a1赋值的代码,有三个表达式,用逗号分开,所以最终的值应该是最后一个表达式的值,也就是(d + 3)的值,为8,所以a1的值为8。
	
	//对于给a2赋值的代码,也是有三个表达式,这时的三个表达式为a2 = ++b,c--,d + 3;
	//(这是因为赋值运算符比逗号运算符优先级高)虽然最终表达式的值虽然也为8,
	//但b = 4(第2行代码运算完时,b = 3,即第3行代码运行时,b的值为4),所以a2 = 4。
	
	printf("%d %d", a1, a2);

	return 0;
}

题⽬4


假设环境是x86环境,程序输出的结果是啥?

int main()
{
	int a[5][5];
	int(*p)[4];
	//p是个数组指针,指向的数组有4个int类型的元素
	p = a;//int(*ptr)[5]=a
	//这里将a赋给p时肯定有类型的差异,但是没关系
	//将数组a第一行的地址赋给p,则p指向了数组a第一行的首元素的地址
	//而p指向的数组是只有4个int类型的元素
	//p[4][2]就是p+1跳过4个整型(可跨行)
	//这里不明白的话请结合以下图5理解一下

	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	//结果为fffffffc  -4
	
	//地址-地址=地址与地址之间的元素个数
	//或者说指针-指针的绝对值得到的是指针和指针之间的元素个数
	//又因为低地址-高地址,得到的是负数(随着数组下标的增长,地址是由低到高变化的)
	//因此&p[4][2] - &a[4][2]结果等于-4
	
	//%d打印出来的是原码的值:-4
	//原码:10000000000000000000000000000100
	//反码:11111111111111111111111111111011
	//补码:11111111111111111111111111111100  ==反码+1
	
	//用%p打印时把结果-4当成了地址,因为%p打印地址看的是内存中的值,内存里面的是什么就打印什么
	//在内存中存的是补码,进去内存中看的时候是显示成16进制给我们看的
	//补码:1111 1111 1111 1111 1111 1111 1111 1100
	//将以上补码翻译成16进制:fffffffc
	//因此用%p打印时,显示出来的是内存中的补码的16进制形式fffffffc
	
	return 0;
}

【图5】

C语言学习笔记---指针(7)---指针完结篇_第5张图片

题⽬5

int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);
	//&aa就是第二行的地址,+1跳过第二行的地址指向外面某一块内存的地址
	int* ptr2 = (int*)(*(aa + 1));
	//aa是二维数组首元素的地址(即第一行的地址)
	//*(aa + 1)==aa[1]第二行的数组名,相当于第二行的一维数组的首元素的地址,即6的地址
	
	//(int*)其实可以删掉,加上是为了迷惑做题者

	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10 5
	//*(ptr1 - 1)指向10的地址
	//*(ptr2 - 1)指向5的地址

	return 0;
}

题⽬6

int main()
{
	char* a[] = { "work","at","alibaba" };
	//a是指针数组,数组里面的元素是指针(存放了地址的元素),每个元素是char*类型的
	//将每一个字符串的起始地址都存放在数组a的每一个元素当中,而每一个元素都是指针
	//第一个指针指向字符串"work\0"
	//第二个指针指向字符串"at\0"
	//第三个指针指向字符串"alibaba\0"

	char** pa = a;
	//将数组a中首元素的地址交给了pa
	//二级指针pa指向a数组中的第一个指针的地址
	pa++;
	//pa+1跳过一个char*的大小指向数组a中的第二个指针的地址
	printf("%s\n", *pa);
	//pa解引用后找到第二个指针,而第二个指针里存放的是字符串"at\0"的地址
	//再通过字符串"at"的地址找到字符串"at\0"

	//不明白的话结合下图6理解

	return 0;
}

【图6】

C语言学习笔记---指针(7)---指针完结篇_第6张图片

题⽬7

int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	//指针数组,c放的是每一个字符串首字符的地址
	
	char** cp[] = { c + 3,c + 2,c + 1,c };
	//c数组名表示首元素的地址,即c数组中存放了"ENTER"首字符地址的指针的地址
	//c+3 表示跳过3个char*的大小,指向a数组中的第4个指针
	//c + 2指向a数组中的第3个指针
	//c + 1指向a数组中的第2个指针
	//c指向a数组中的第1个指针

	//cp每一个元素是char**

	char*** cpp = cp;
	//cpp存放了cp首元素的地址,类型是char***

	//以上不明白的,请结合以下图7理解

	printf("%s\n", **++cpp);//"POINT"
	//结合图8理解
	printf("%s\n", *-- * ++cpp + 3);//ER
	//在这里+3的优先级是最低的
	//结合图9理解

	printf("%s\n", *cpp[-2] + 3);//ST
	//cpp[-2]==*(cpp-2)即cpp的地址后退2个char***
	//结合图10理解
	printf("%s\n", cpp[-1][-1] + 1);//EW
	//cpp[-1][-1]==*(*(cpp-1)-1)
	//cpp[-1]==*(cpp-1)即cpp的地址后退1个char***
	//*(cpp - 1)==c+2
	//*(cpp-1)[-1]==*(c+2-1)==*(c+1)
	//结合图11理解

	return 0;
}

【图7】

C语言学习笔记---指针(7)---指针完结篇_第7张图片

【图8】

C语言学习笔记---指针(7)---指针完结篇_第8张图片

【图9】

C语言学习笔记---指针(7)---指针完结篇_第9张图片

【图10】

C语言学习笔记---指针(7)---指针完结篇_第10张图片

【图11】

C语言学习笔记---指针(7)---指针完结篇_第11张图片

自我感悟:
一级指针和二级指针只需解引用*一次就找到最终值
三级指针需解引用**两次才找到最终值

需要强调一下:
我们打印字符串数组的时候传给printf()的其实是首字符的地址

int main()
{
	char(*arr)[] = "abcdef";//数组指针,指向了数组的指针,数组的每一个元素是char型
	printf("%s\n", arr);//传a的地址过去,然后从a开始往后数全部打印出整个字符串
	return 0;
}

总结

关于指针的学习一定要学会画图!,画一个正确的图能够事半功倍!

至此,指针部分的重要内容已经全部学习完了,这一篇博客是指针的完结篇,希望对于指针问题有些困惑的伙伴们有所帮助。

由于临近期末,博客需要断更两三个星期,寒假继续努力学习,共勉!

你可能感兴趣的:(C语言初阶学习笔记,c语言,学习,笔记,c++)