深入理解指针5

news2025/5/25 17:03:23

sizeof和strlen的对比

sizeof的功能

**sizeof是**** 操作符****,用来**** 计算****变量或类型或数组所占**** 内存空间大小****,**** 单位是字节,****他不管内存里是什么数据**
int main()
{
	printf("%zd\n", sizeof(char));
	printf("%zd\n", sizeof(signed char));
	printf("%zd\n", sizeof(unsigned char));
	printf("%zd\n", sizeof(unsigned int));
	printf("%zd\n", sizeof(signed int));
	printf("%zd\n", sizeof(int));
	printf("%zd\n", sizeof(short));
	printf("%zd\n", sizeof(long));
	printf("%zd\n", sizeof(float));
	int a = 10;
	printf("%zd\n", sizeof a);
	int arr[] = { 1,2,3,4 };
	printf("%zd\n", sizeof arr);  //sizeof如果是统计变量或数组大小,括号就可以省略,如果是统计类型大小,括号就不可以省略
	return 0;
}

分析:
1.i是一个整型变量,i+20后还是一个整型变量,int类型占4个字节,而short类型占2个字节,所以当你把一个int类型的值赋值给short类型,他肯定放不下,
放不下就会发生截断,但是不管s变量里面最终放的是什么,这个表达式其实是不会执行的,i也不会被重新赋值,编译器之关心s变量是什么类型,


2.当sizeof操作数为表达式的时候,是不会计算表达式的值,有也只是摆设,我们都知道一个.c的文件要执行,必须要变成.exe可执行文件才行
要经过编译和链接两个部分,在编译器编译的时候,计算大小的时候其实是根据类型计算的,就直接将sizeof(s=i+20),转换成sizeof(s),那s是short类型
在内存中占两个字节,

3.那为什么sizeof操作数为表达式的时候,就不计算表达式的值呢?
表达式是由操作数和操作符(运算符)组成的式子,能计算出一个值,比如a[3],[]是下标运算符,a是操作数,表示数组名,3也是一个操作数,是一个常量。
表达式有两个属性,一个是值属性,一个是类型属性,当表达式得不到结果的时候,sizeof就会计算他的类型


```c int main() { short s = 10; int i = 2; printf("%d\n", sizeof(s = i + 20)); printf("%d\n", i); return 0; } ```

总结:

  1. sizeof是操作符计算的所占内存大小,单位是字节,不关心内存中存放什么数据
  2. sizeof操作符的操作数如果是表达式,不会计算表达式的值,编译器会转换成计算类型的大小
  3. 当sizeof计算变量或数组大小时,括号可以省略,但是计算类型的时候,大小不能省略
  4. sizeof的返回类型,是size_t类型,它对应的格式是%zd

strlen的功能

** strlen是函数,使⽤需要包含头⽂件 **`**
int main()
{
	int str[] = { 1,2,3,4,5,6,7 };
	printf("%zd\n", strlen(str));//结果是1,为什么是1呢?调试观察内存窗口的时候会
	//1后面就是00,而00就是\0
	return 0;
}

strlen函数遇到\0就停止统计

int main()
{
	char arr[] = {'a','b','c','d','e'}; //字符数组存的都是字符,所以arr数组内存里没有\0,就会越界访问,一直遇到\0为止
	char arr2[] = "hello";				//字符数组里存的是字符串,字符串末尾有隐藏的\0
	printf("%zd\n", strlen(arr));
	printf("%zd\n", strlen(arr2));
	return  0;
}

只要没有遇到\0就会一直往后统计,所以会存在越界访问的情况

总结:

  1. strlen是库函数,使用是需要包含头文件**<font style="color:#117CEE;"><string.h></font>**,统计\0之前的字符串的长度,特别关心内存里有没有\0,因为只有遇到\0才会停止统计
  2. size_t strlen ( const char * str ), 他的返回类型也是size_t的类型,对应的格式也是%zd,他的参数是cha*类型的指针

数组和指针练习题

除了下面的两个例子外,你见到的任何数组名都是表示数组首元素的地址
  1. sizeof(数组名)——数组名单独放在sizeof里表示计算的是整个数组的大小,而不是计算首元素地址的大小
  2. &数组名——表示取出整个数组的地址,
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%zd\n", sizeof(a));//16,因为他取出的是整个数组的大小

	printf("%zd\n", sizeof(a + 0));//a并没有单独放在sizeof里,所以他表示的是数组名,数据名就是
	//首元素的地址,是地址,就是4或8个字节

	printf("%zd\n", sizeof(*a));//4,a并没有单独放在sizeof里,所以他表示的是数组名,
	//首元素的地址解引用得到的就是首元素1,*a=a[0]

	printf("%zd\n", sizeof(a + 1));//a并没有单独放在sizeof里,所以他表示的是数组名,地址+1
	//因为是整型数组,所以跳过一个字节,指针指向第二个元素的地址,还是地址,是地址就是4或8个字节
	
	printf("%zd\n", sizeof(a[1]));
	//a[1]表示第二个元素,计算第二个元素的大小,因为是int类型,所以是四个字节

	printf("%zd\n", sizeof(&a));
	//&a取出整个数组的地址,数组地址也是地址,是地址就是4或8个字节

	printf("%zd\n", sizeof(*&a));
	//这个题可以从两个角度分析,&a取地址,那么*&a就是将地址解引用,那他他们就抵消了,所以 sizeof(*&a)=sizeof(a),就是计算整整个数组的大小
	//另一个角度就是: &a取出来的是一个数组的地址,将数组的地址存放起来用数组指针,int(*p)[4]=&a, 
	//声明了一个数组指针 p,它指向一个包含 4 个 int 类型元素的数组。&arr 是取数组 arr 的地址,将其赋值给 p,此时 p 指向了 arr 这个数组
	//对指针 p 解引用,即* p,根据指针解引用的原理,得到的是指针所指向的对象,在这里 p指向一个数组,所以* p就代表了这个数组,
	//它和直接使用数组名 arr 效果类似
	//sizeof 是一个操作符,它返回操作数在内存中占用的字节数。因为 *p 代表整个数组,那谁能代表整个数组呢,不就是数组名吗
	// 所以 sizeof(*ptr) 计算的就是这个包含 5 个 int 元素的数组在内存中所占的字节数。


	printf("%zd\n", sizeof(&a + 1));
	//&a得到是数组的地址,而数组的地址其实就是首元素的地址,而数组地址+1是跳过一个数组,
	//所以&a+1指向的其实是下一个数组的地址,但还是地址,是地址,就是4或者8个字节

	printf("%zd\n", sizeof(&a[0]));
	//他的意思是说取出首元素的地址,只要是地址就是4或者8个字节

	printf("%zd\n", sizeof(&a[0] + 1));
	//首元素的地址+1,指向第二个元素的地址,那不还是地址吗,是地址就是4或8个字节

	return 0;
}

int main()
{

	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));
	//数组名a单独放在sizeof里,计算的是整个数组的大小,数组里一共有12个元素,每个元素的类型都是4个字节,所以是48个字节
	//结果是48


	printf("%d\n", sizeof(a[0][0]));
	//a[0][0]表示数组的第一行第一列个元素,也就是0,这个元素的类型是int类型,所以占4个字节


	printf("%d\n", sizeof(a[0]));
	//a[0]=*(a+0),那a是数组名,即二维数组首元素的地址,二维数组首元素是第一行,也就是第一行的地址,第一行的地址解引用得到是第一行的数组名
	//而数组名又表示数组首元素的地址,所以a[0]其实表示是第一行的数组名,数组名单独放在sizeof里,就不是代表数组首元素的地址了,而是计算
	//第一行数组的大小,第一行数组有4个元素,每一个元素都是int,一个int4个字节,所以是16个字节


	printf("%d\n", sizeof(a[0] + 1));
	//a[0]是第一行的数组名,数组名没有单独放在sizeof里,所以表示第一行数组首元素的地址,是int*类型,int*类型指针+1,跳过一个整型元素
	//所以a[0]+1表示第一行第二列的元素的地址,还是地址
	//是地址大小就是4或8个字节


	printf("%d\n", sizeof(*(a[0] + 1)));
	//a[0]+1表示的是第一行第二列元素的地址,那解引用后就得到第一行第二列这个元素,是int类型的,所以是4个字节
	//结果是4个字节


	printf("%d\n", sizeof(a + 1));
	//a是数组名,数组名没有单独放在sizeof里,所以表示的是第一行,也就是第一个一维数组的地址,a+1表示第二行的地址,是地址大小就是4或8个字节


	printf("%d\n", sizeof(*(a + 1)));
	//a+1是第二行的地址,是一个数组指针类型,数组指针解引用得到的就是的第二行的数组名,数组名单独放在sizeof里表示计算第二行数组的大小
	//他等同于sizeof(a[1]),因为*(a+1)=a[1]
	//结果是16字节


	printf("%d\n", sizeof(&a[0] + 1));
	//a[0]是第一行的数组名,数组名前面加上&,表示取出第一行数组的地址,数组地址+1跳过一个数组,所以&a[0] + 1表示第二行的地址
	//是地址大小就是4或8个字节,他的写法等价于a+1


	printf("%d\n", sizeof(*(&a[0] + 1)));
	//&a[0] + 1表示第二行的地址,然后解引用,一个数组指针解引用得到的是数组名,所以*(&a[0] + 1))表示的是第二行的数组名,即统计第二行数组元素
	//的大小,结果是16个字节,他的写法其实就等价于sizeof(a[1])


	printf("%d\n", sizeof(*a));
	//a在这里没有单独放在sizeof里,也没有加上&,就表示数组首元素的地址,也就是第一行的地址
	//第一行的地址解引用后,就得到第一行的数组名,数组名单独放在sizeof里,表示计算第一行数组元素的大小
	//结果是16个字节


	printf("%d\n", sizeof(a[3]));
	//a[3]表示第四行的数组名,数组名单独放在sizeof里,就是计算第四行数组元素的大小,但是这个数组只有3行,第4行属于越界访问了
	//但是对于sizeof来说,sizeof并不关心这个数组元素是不是真的存在,他只关心a[3]的数据类型,因为sizeof 是一个编译时操作符,它的作用是在编译阶段
	// 计算某个数据类型或者变量所占的内存字节数。它不会在运行时去访问实际的内存地址,只是根据数据类型的定义来进行计算。
	//从类型上来说,他和a[0],a[1],a[1]一样,都是包含4个int类型的元素的一维数组
	//结果是16个字节

	return 0;
}


int main()
{

	char arr[] = { 'a','b','c','d','e' };
	printf("%d\n", sizeof(arr));
	//数组名单独放在sizeof里,表示计算整个数组的大小,一共有5个元素,每个元素是char类型,
	//结果是5个字节

	printf("%d\n", sizeof(arr + 0));
	//arr没有单独放在sizeof里,所以表示的数组一维数组首元素的地址,是地址大小就是4或8字节
	printf("%d\n", sizeof(*arr));
	//首元素的地址解引用就得到首元素,是一个char类型的,所以是1个字节

	printf("%d\n", sizeof(arr[1]));
	//arr+1表示第二个元素,数组元素类型都是相同的,char类型的,所以是1个字节

	printf("%d\n", sizeof(&arr));
	//数组名前加上地址表示取出数组的地址,是地址,大小就是4或8个字节

	printf("%d\n", sizeof(&arr + 1));
	//&arr+1,数组的地址+1,跳过一个数组,指向下一个数组的地址,还是地址,是地址大小就是4或8个字节

	printf("%d\n", sizeof(&arr[0] + 1));
	//取出第一个元素的地址,是char*类型的,+1,跳过一个字节,指向第二个元素的地址,是地址大小就是4或8个字节
return 0;
}
int main()
{
	char arr[] = { 'a','b','c','d','e'};
	printf("%zd\n", strlen(arr));
	//arr是数组首元素的地址,从首元素这个地址开始向后, 统计\0 之前字符串中字符的个数,一直找到\0为止,但是这个数组内存中
	//并没有存入\0,所以当把整个数组的字符都统计完了,还会往后继续统计,直到遇到\0,所以最后的结果是随机值

	printf("%zd\n", strlen(arr + 0));
	//首元素地址+0还是首元素地址,那么strlen统计的时候从首元素地址开始统计,和上面的一样,可能会存在越界查找
	//直到遇到\0,所以最终的打印结果还是随机值

	printf("%zd\n", strlen(*arr));
	//arr是数组首元素的地址,地址解引用后就得到首元素,所以得到字符a,字符a所对应的acsii码值是97,但是因为strlen的参数是一个
	//char *类型的指针,存放的是地址,所以他就会把97当成地址访问,但是97这个地址不是你随便想访问就能访问的,所以会报错,提示你非法访问

	printf("%zd\n", strlen(arr[1]));
	//arr[1]表示第二个字符,即b,而b的ASCII码值是98,同样的,会把98当成地址访问,最后会报错,提示非法访问

	printf("%zd\n", strlen(&arr));
	//&arr表示数组的地址,数组的地址是数组指针类型,也就是char (*p)[6],但是strlen函数的参数是char*类型的,所以编译器
	//就会把数组指针类型转换成char*类型的,但是值不发生变化,然后数组的地址其实是首元素的地址,从他开始统计的
	//时候,找到\0才结束,所以最后的结果也是随机值

	printf("%zd\n", strlen(&arr + 1));
	//&arr是数组的地址,数组地址+1,指向下一个数组的地址,就从下一个数组的首元素地址开始统计,直到遇到\0,所以最后也是随机值

	printf("%zd\n", strlen(&arr[0] + 1));
	//&arr[0]是首元素地址,首元素地址+1,指针指向第二个元素的地址,从第二个元素的地址开始往后统计,直到遇到\0
	//所以最后的结果也是一个随机值

	return 0;
}
int main()
{
	char arr[] = "abcedf"; //内存里有\0

	printf("%zd\n", strlen(arr));
	//结果是6,strlen函数统计\0之前的字符个数

	printf("%zd\n", strlen(arr + 0));
	//结果是6

	//printf("%zd\n", strlen(*arr));
	//*arr是字符a,strlen的参数是char*类型指针,a的ascii吗值是97,会把97当成一个地址,属于非法访问

	//printf("%zd\n", strlen(arr[1]));
	//得到的是字符b,字符b的ascii码值是98,同理他也会把98当成地址访问,属于非法访问

	printf("%zd\n", strlen(&arr));
	//取出数组的地址,从数组的地址也就是首元素的地址开始往后统计,直到遇到\0
	//结果是6

	printf("%zd\n", strlen(&arr + 1));
	//数组地址+1,指向下一个数组的地址,那从下一个数组的地址开始往后统计,你也不知道什么时候,会遇到\0
	//随机值

	printf("%zd\n", strlen(&arr[0] + 1));
	//取出第一个元素的地址,然后+1,指向第二个元素的地址,从第二个元素的地址开始往后统计,
	//结果是5
	return 0;
}
int main()
{
	char arr[] = "abcdef";//内存有\0
	printf("%zd字节\n", sizeof(arr));
	//数组名单独放在sizeof里,表示计算整个数组的大小,
	//结果是7个字节

	printf("%zd字节\n", sizeof(arr + 0));
	//注意这题,不是计算整个数组的大小,因为arr不是单独放在sizeof里,所以这里表示数组首元素的地址
	//arr+0还是arr,是地址,所以结果是4或8个字节

	printf("%zd字节\n", sizeof(*arr));
	//*arr表示首元素,也就是字符a,a是一个char类型,占一个字节
	//结果是1个字节

	printf("%zd字节\n", sizeof(arr[1]));
	//arr[1]表示字符b,b是一个char类型的,占1个字节

	printf("%zd字节\n", sizeof(&arr));
	//取出数组的地址,是地址,大小就是4或8个字节

	printf("%zd字节\n", sizeof(&arr + 1));
	//&arr+1表示下一个数组的地址,还是地址,是地址,大小就是4或8个字节

	printf("%zd字节\n", sizeof(&arr[0] + 1));
	//%arr[0]表示取出数组首元素的地址,+1,指向第二个元素的地址
	//是地址大小就是4或8个字节
	
	return 0;
}
int main()
{
	char* p = "abcdef";  //定义一个字符指针,指针指向的对象为字符,存放的其实是常量字符串字符a的地址
	printf("%d\n", sizeof(p)); 
	//p是一个指针变量,指针变量就是用来存放地址的啊,那是地址就占内存4或8个字节,
	// 计算的是指针 p 本身所占用的内存大小

	printf("%d\n", sizeof(p + 1));
	//p是char*类型的指针,char*类型的指针+1,跳过一个字节,p存放的是a的地址,又因为数组在内存中是连续存放的
	//所以p+1指向b的地址,是地址,大小就是4或8个字节


	printf("%d\n", sizeof(*p));
	//p是存的a的地址,解引用,就得到字符a,字符a是char类型的,在内存中占一个字节
	//结果为1

	printf("%d\n", sizeof(p[0]));
	//p[0]=*(p+0),p存放a的地址,+0,没有变化,解引用就得到p指向的对象,也就是字符a
	//a是char类型,在内存中占1个字节

	printf("%d\n", sizeof(&p));
	//p是一个指针变量,是变量就要向内存申请空间,所以p也有自己的地址
	//&p取出来的就是p的地址,是地址大小就是4或8个字节,存放一级指针变量的地址,称为二级指针,

	printf("%d\n", sizeof(&p + 1));
	//结果是4或8个字节

	printf("%d\n", sizeof(&p[0] + 1));
	//&p[0]=&(*(p+0)),p存的是首元素的地址,首元素的地址解引用后得到字符a,&a,取出来的地址是char*类型
	// 因为是char*类型,所以指针+1跳过一个字节,指向b,还是char*类型的指针
	//是指针,也就是地址,大小就为4或8个字节

	return 0;
}

**<font style="color:#DF2A3F;">关于printf("%d\n", sizeof(&p + 1))这句代码的解析如下:</font>**

p本身是一个char*类型的指针变量,是变量就要向内存申请空间,所以p也有自己的地址,&p取出来的是一个一级指针变量的地址,他是一个二级指针,他的类型是char**;

如果是p+1,因为p是一个char*类型的指针,所以p+1跳过一个字节

而对于&p+1,&p是char**类型。

在指针加减运算时,指针+n,移动的字节数其实是n*该指针指向的类型大小。

因为&p是char**类型,他所指向是char*,这里就涉及到之前学过的知识,指针变量的大小,不管你是什么指针,char*,char**,long*,指针变量的大小都是一样的,如果是32位的系统就是4个字节,如果是64位的就是8个字节

所以&p+1移动4个字节,&p+1仍然是一个char**类型的指针,既然 是指针,大小就是4或8个字节。

```c int main() { printf("%d\n", sizeof(char**)); printf("%d\n", sizeof(char*)); printf("%d\n", sizeof(char**)); printf("%d\n", sizeof(int**)); printf("%d\n", sizeof(int***)); printf("%d\n", sizeof(long*));
return 0;

}


```c
int main()
{
	char* p = "abcdef";   //定义字符指针p,存放的其实是字符a的地址
	printf("%d\n", strlen(p)); //strlen函数遇到\0才会停止
	//从字符a的地址开始往后统计,因为是abcdef是常量字符串,所以末尾会有隐藏的\0,
	//结果是6

	printf("%d\n", strlen(p + 1));
	//p是char*类型的指针,所以+1后跳过一个字符,1个字节,指向b,
	//从b的地址开始往后统计,直到遇到\0,结果是5

	/rintf("%d\n", strlen(*p));
	//p是字符a的地址,解引用后就得到字符a,字符a的ascii码值是97,但是因为strlen函数的参数是const char * str ,是一个char*类型的指针
	//所以他就会把97当成地址,但是这个地址不是你想访问就能访问的,属于非法访问

	printf("%d\n", strlen(p[0]));
	//p[0]等于*(p+0),也就是得到的还是字符a,同理还是会把97当成地址,属于非法访问

	printf("%d\n", strlen(&p));
	//&p,取出的是指针变量p的地址,从指针变量p的地址开始往后统计,你也不知道什么时候会遇到\0
	//随机值

	printf("%d\n", strlen(&p + 1));
	//&p取出来的是指针变量p在内存区域的起始地址,是一个char**类型的,+1后跳过4个字节,从&p+1这个地址开始往后统计,你也不知道什么
	//时候会遇到\0,所以打印的结果是一个随机值


	printf("%d\n", strlen(&p[0] + 1));
	//因为&p[0]得到的其实还是字符a的地址,是一个char*类型的,+1后跳过一个字节,指向字符b,从b的地址开始往后统计
	//结果是5

	//指针变量p自身的地址
	printf("指针变量 p 自身的内存地址(&p): %p\n", &p);

	//指针变量p中存储的地址,也就是字符'a'的地址
	printf("指针变量 p 中存储的地址(p): %p\n", p);

	//字符串首字符'a'的地址
	printf("字符串首字符 'a' 的地址: %p\n", (void*)&("abcdef"[0]));
	return 0;
}

指针运算笔试题

```c int main() { //结果是2和5 int a[5] = { 1, 2, 3, 4, 5 }; int* ptr = (int*)(&a + 1); //&a取出的是数组的地址,数组地址+1,跳过一个数组,得到的指针是数组指针类型,然后强制类型转换成int* //因为ptr是一个int*类型的指针,所以-1,跳过一个整型,4个字节,指向5,解引用就得到5 printf("%d,%d", *(a + 1), *(ptr - 1)); //a表示数组首元素地址,是int*类型的指针,+1后跳过一个整型,也就是4个字节,指向2,解引用就得到2 return 0; }

---

```c
//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结构是啥?
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;	//定义一个结构体类型的指针,指针指向的对象是结构体

int main()
{
	printf("%p\n", p + 0x1);	//0x开头表示十六进制,0x1就是1,所以这个式子就是p+1
	//那指针+1跳过多少个字节呢?因为p是一个结构体指针,所以p+1跳过了一个结构体,
	// 这里题目说了结构体是20个字节,所以p+1跳过了20个字节
	// p+1=0x100000+20,注意不能这样写,20是十进制,要转成十六进制,20对应的十六进制为14
	// 所以结果为0x100014

	printf("%p\n", (unsigned long)p + 0x1);
	//注意这题也有个坑,p不是指针类型了,他强制转换成了无符号整型,那这里p+1就是0x100001

	printf("%p\n", (unsigned int*)p + 0x1);
	//这里p是一个无符号整型指针,无符号整型是4个字节,所以p+1跳过4个字节
	//所以结果是0x100004
	return 0;
}

//下面的代码要改成x64的环境才能运行
int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	//&a取出数组的地址,然后+1,跳过一个数组,指针指向下一个数组的地址,数组地址是数组指针类型,但是把他强制类型转换成了int*,ptr1是一个int*类型指针
	//那么ptr-1,就是跳过一个整型元素,指向元素为4的地址,然后解引用,得到的是4
	//ptr[-1]其实就等于*(ptr+(-1)),也就是 * (ptr-1),十进制4转成十六进制4还是4
	
	int* ptr2 = (int*)((int)a + 1);
	//只有指针才讨论是跳过一个字节还是4个字节,整型数据+1就是+1

	//a表示数组首元素地址,是int*类型,但是强制类型转换成了int,如果是int*类型,那加1是跳过4个字节,但是现在强制类型转化
	//int* ptr2 = (int*)((long long )a + 1);//这样写就可以在x64的环境下运行,因为long long是8个字节

	printf("%x,%x", ptr1[-1], *ptr2);//%X表示以十六进制形式打印
	//打印结果是4 和 2000000
	return 0;
}
为什么上面的代码在x64的环境下运行不了?

问题出在int* ptr2 = (int*)((int)a + 1)这个代码中,因为是x64的环境,指针变量的大小为8个字节,

假设a的地址是0x1122334455667788,把他强制类型转化int,因为int是四个字节,所以就会发生截断,

截断后就变成了:0x 55667788,那这个地址就不对了,那这个地址+1就不知道是谁的空间了,属于非法访问

非要强制类型转换就转换成long long这个类型,因为long long是8个字节

![](https://i-blog.csdnimg.cn/img_convert/96dbef57d6813649c312f2a3ff25401a.png)
注意!不要被这题的陷阱迷惑了,他是用的圆括号,而不是花括号,这是一个逗号表达式,
逗号表达式按照从左到右的顺序依次计算,整个表达式的值是最后一个表达式的值。
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	//他其实是这样的:int a[3][2] = { 1, 3, 5 };
	//第一行:1,3
	//第二行:5,0
	//第三行:0,0

	int* p;
	p = a[0];
	//a[0]是第一行的数组名,他会隐式转换成第一行首元素的地址,
	printf("%d", p[0]);
	//p[0] = *(p+0),第一行首元素地址解引用就得到1,也就是第一行第一个元素
	return 0;
}
什么是逗号表达式?

逗号表达式就是由逗号运算符将多个子表达式连接,这题的(0, 1)中0和1都是常量,常量也是一种表达式

按照从左到右的顺序依次计算这些子表达式,最终整个逗号表达式的值是最后一个子表达式的值。

```c 逗号表达式会依次计算每个子表达式,并且最终会返回最后一个子表达式的值。这个值可以被赋值给其他变量或者用于其他表达式的计算。 变量定义语句中的逗号并不涉及求值后返回结果的操作。它只是告知编译器为多个变量分配内存并初始化。每个变量初始化后的值会分别存储在对应的变量内存空间中, 不存在一个统一的 “表达式结果”

变量定义语句中的逗号不是运算符,不存在优先级和结合性的概念。它只是语法规定的分隔符,用于区分不同变量的定义。
逗号表达式中的逗号是运算符,它的优先级是所有运算符中最低的。结合性是从左到右,也就是按照从左到右的顺序依次计算各个子表达式。
所以在使用逗号表达式时,通常需要用括号来明确运算顺序,避免因优先级问题导致意外结果。
int main()
{

int a = 1, b = 2, c = 3;//变量定义语句中的逗号不属于逗号表达式,这里的逗号的作用是定义多个同类型的变量


int x = 1, y = 2;
int z = x + 1, y + 2;  // 这里由于逗号运算符优先级低,会先计算 x + 1 并赋值给 z,y + 2 不会影响结果
int z = (x + 1, y + 2);  // 这里使用括号明确是逗号表达式,z 的值为 y + 2 的结果


int a = 1, b = 2, c = 3;
int result;
result = (a = a + 1, b = b + 2, c = c + 3);//result 被赋值为逗号表达式最后一个子表达式 c = c + 3 的值,程序输出时会显示 result 等于更新后 c 的值。
printf("a = %d, b = %d, c = %d, result = %d\n", a, b, c, result);
return 0;

}


---

```c

int main()
{
	int a[5][5];//定义一个二维数组,该数组有5行5列
	int(*p)[4];//定义一个指针p,该指针指向1个包含4个int类型一维数组
	p = a;//将二维数组首元素的地址,也就是第一行的地址赋值给p,虽然a和p都是地址
	//但是他们的类型是不同的,a是一个指向5个int类型元素的一维数组指针,int(*)[5]
	//而p是一个指向4个int类型元素的一维数组指针,int(*)[4]
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//
	//%p:用于以十六进制的形式输出指针的值,这里输出的是 -4 的十六进制表示。
	// 在大多数系统中,int 类型是 32 位的,-4 的补码表示为 0xFFFFFFFC。

	return 0;
}

&p[4][2]:

由于p是一个指向包含4个int类型的元素的一维数组指针,所以p[4]会让指针p向后移动4个包含4个int元素的一维数组的距离,可以把p[4]理解成*(p+4),a[4]=*(a+4),a[4]会让指针a向后移动4个包含5个int元素的一维数组的距离

指针减指针的结果是两个指针之间相差的元素的个数,这里&p[4][2]-&a[4][2]得到的是负数,因为&p[4][2]的地址靠前,要小一些,

最终的打印结果为-4,-4是他的原码的十进制形式,他存储在内存中是要以补码的形式

-4原码:10000000000000000000000000000100

-4反码:11111111111111111111111111111011

-4补码:1111 1111 1111 1111 1111 1111 1111 1110

地址:fffffffc(4个1为一个f)

如果是要打印地址的话,不用转成原码,直接打印补码,因为地址是16进制,所以要将二进制的补码转成十六进制


![第五题分析过程](https://i-blog.csdnimg.cn/img_convert/f6b408013601c800333f983ac50e04ec.png)
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+1得到是第二行的地址,行地址解引用得到的是该行首元素的地址

	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//打印结果为10,5
	//ptr1和ptr2都是int*类型的指针,减1时,就跳过一个整形元素
	return 0;
}

int main()
{
	char* a[] = { "work","at","alibaba" };//定义一个字符指针数组
	//下标为0的元素存放的是常量字符串"work"中'w'的地址
	//下标为1的元素存放的是'a'的地址串"at"中'a'的地址
	//下标为2的元素存放的是'a'的地址串"alibaba"中'a'的地址

	char** pa = a;//a表示数组首元素的地址,是char**类型
	pa++;
	printf("%s\n", *pa);//pa++后,然后解引用得到的是at"中'a'的地址
	//打印结果为at
	return 0;
}

第七题分析过程


int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };//定义了一个字符指针数组
	//分别存放字符'E','N','p','F'的地址
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
    
	printf("%s\n", **++cpp);	//打印"POINT"
	printf("%s\n", *-- * ++cpp + 3);	//打印"ER"
	//++和--和*的优先级是相同的,都是右结合,从右往左,他们的优先级都高于+号
	//在优先级相同的时候,运算顺序由结合方向决定,
	//第一步:++cpp   第二步:* ++cpp 第三步:-- * ++cpp  第四步:*-- * ++cpp   第五步:+3

	printf("%s\n", *cpp[-2] + 3);	//打印"ST"
	//[]的优先级高于*,先执行cpp[-2],然后再解引用
	//cpp[-2]=*(cpp-2)
	//*cpp[-2] + 3 =*( *(cpp-2)) + 3
	printf("%s\n", cpp[-1][-1] + 1);	//打印"EW"
	//cpp[-1][-1]其实就等于*(*(cpp-1)-1)
	//cpp-1后,然后解引用,拿到的是c+2这个地址,c+2这个地址再减1,得到的是c+1这个地址,c+1这个地址解引用,得到的是N的地址,然后再+1,得到的是E的地址
	return 0;
}

上面这个题产生的疑问以及解答:
1.cp是数组名,那不是数组首元素的地址吗,那cpp+1后,cp指向的地址会不会发生改变吗?因为cpp不是等于cp吗

解答:
cp是一个数组,类型为char [4],即包含4个char类型元素的数组。在c语言里,数组名表示首元素的地址,他是一个常量地址,在程序运行期间
是不能被修改的,cp所指向的地址不会因为其他指针的操作而改变

  1. char ** 类型的指针执行 +1 操作时,指针会在内存中向后移动一个 char * 类型对象,对吗?
    解答:
    是的。指针运算和指针所指向的类型有关,在C语言中,指针加上一个整数时,实际移动的字节数是n * sizeof(指针所指向的类型)
![分析过程](https://i-blog.csdnimg.cn/img_convert/f5a100966cd651ef347c94eeaa33fb44.png)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2325753.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文详解QT环境搭建:Windows使用CLion配置QT开发环境

在当今的软件开发领域&#xff0c;跨平台应用的需求日益增长&#xff0c;Qt作为一款流行的C图形用户界面库&#xff0c;因其强大的功能和易用性而备受开发者青睐。与此同时&#xff0c;CLion作为一款专为C/C打造的强大IDE&#xff0c;提供了丰富的特性和高效的编码体验。本文将…

NE 综合实验3:基于 IP 配置、链路聚合、VLAN 管理、路由协议及安全认证的企业网络互联与外网访问技术实现(H3C)

综合实验3 实验拓扑 设备名称接口IP地址R1Ser_1/0与Ser_2/0做捆绑MP202.100.1.1/24G0/0202.100.2.1/24R2Ser_1/0与Ser_2/0做捆绑MP202.100.1.2/24G0/0172.16.2.1/24G0/1172.16.1.1/24G0/2172.16.5.1/24R3G5/0202.100.2.2/24G0/0172.16.2.2/24G0/1172.16.3.1/24G0/2172.16.7.1/…

多段圆弧拟合离散点实现切线连续

使用多段圆弧来拟合一个由离散点组成的曲线,并且保证切线连续。也就是说&#xff0c;生成的每一段圆弧之间在连接点处必须有一阶导数连续&#xff0c;也就是切线方向相同。 点集分割 确保每个段的终点是下一段的起点&#xff0c;相邻段共享连接点&#xff0c;避免连接点位于数…

【蓝桥杯】第十四届C++B组省赛

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;蓝桥杯 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 试题A&#xff1a;日期统计试题B&#xff1a;01串的熵试题C&#xff1a;冶炼金属试题D&#xff1a;飞机降落试题E&#xff1a;接…

企业级海外网络专线行业应用案例及服务商推荐

在全球化业务快速发展的今天&#xff0c;传统网络技术已难以满足企业需求。越来越多企业开始选择新型海外专线解决方案&#xff0c;其中基于SD-WAN技术的企业级海外网络专线备受关注。这类服务不仅能保障跨国数据传输&#xff0c;还能根据业务需求灵活调整网络配置。接下来我们…

阿里云服务器安装docker以及mysql数据库

(1) 官方下载路径 官方下载地址: Index of linux/static/stable/x86_64/阿里云镜像地址: https://mirrors.aliyun.com/docker-ce/下载最新的 Docker 二进制文件&#xff1a;wget https://download.docker.com/linux/static/stable/x86_64/docker-20.10.23.tgz登录到阿里云服务…

深入解析:HarmonyOS Design设计语言的核心理念

深入解析&#xff1a;HarmonyOS Design设计语言的核心理念 在当今数字化迅速发展的时代&#xff0c;用户对操作系统的体验要求越来越高。华为的HarmonyOS&#xff08;鸿蒙操作系统&#xff09;应运而生&#xff0c;旨在为用户提供全场景、全设备的智慧体验。其背后的设计语言—…

dfs记忆化搜索刷题 + 总结

文章目录 记忆化搜索 vs 动态规划斐波那契数题解代码 不同路径题解代码 最长递增子序列题解代码 猜数字大小II题解代码 矩阵中的最长递增路径题解代码 总结 记忆化搜索 vs 动态规划 1. 记忆化搜索&#xff1a;有完全相同的问题/数据保存起来&#xff0c;带有备忘录的递归 2.记忆…

【Linux】进程的详讲(中上)

目录 &#x1f4d6;1.什么是进程? &#x1f4d6;2.自己写一个进程 &#x1f4d6;3.操作系统与内存的关系 &#x1f4d6;4.PCB(操作系统对进程的管理) &#x1f4d6;5.真正进程的组成 &#x1f4d6;6.形成进程的过程 &#x1f4d6;7、Linux环境下的进程知识 7.1 task_s…

优选算法的巧思之径:模拟专题

专栏&#xff1a;算法的魔法世界 个人主页&#xff1a;手握风云 目录 一、模拟 二、例题讲解 2.1. 替换所有的问号 2.2. 提莫攻击 2.3. Z字形变换 2.4. 外观数列 2.5. 数青蛙 一、模拟 模拟算法说简单点就是照葫芦画瓢&#xff0c;现在草稿纸上模拟一遍算法过程&#xf…

【云服务器】在Linux CentOS 7上快速搭建我的世界 Minecraft 服务器搭建,并实现远程联机,详细教程

【云服务器】在Linux CentOS 7上快速搭建我的世界 Minecraft 服务器搭建&#xff0c;详细详细教程 一、 服务器介绍二、下载 Minecraft 服务端三、安装 JDK 21四、搭建服务器五、本地测试连接六、添加服务&#xff0c;并设置开机自启动 前言&#xff1a; 推荐使用云服务器部署&…

文本分析(非结构化数据挖掘)——特征词选择(基于TF-IDF权值)

TF-IDF是一种用于信息检索和文本挖掘的常用加权算法&#xff0c;用于评估一个词在文档或语料库中的重要程度。它结合了词频&#xff08;TF&#xff09;和逆文档频率&#xff08;IDF&#xff09;两个指标&#xff0c;能够有效过滤掉常见词&#xff08;如“的”、“是”等&#x…

【JavaSE】小练习 —— 图书管理系统

【JavaSE】JavaSE小练习 —— 图书管理系统 一、系统功能二、涉及的知识点三、业务逻辑四、代码实现4.1 book 包4.2 user 包4.3 Main 类4.4 完善管理员菜单和普通用户菜单4.5 接着4.4的管理员菜单和普通用户菜单&#xff0c;进行操作选择&#xff08;1查找图书、2借阅图书.....…

多线程(多线程案例)(续~)

目录 一、单例模式 1. 饿汉模式 2. 懒汉模式 二、阻塞队列 1. 阻塞队列是什么 2. 生产者消费者模型 3. 标准库中的阻塞队列 4. 自实现阻塞队列 三、定时器 1. 定时器是什么 2. 标准库中的定时器 欢迎观看我滴上一篇关于 多线程的博客呀&#xff0c;直达地址&#xf…

一个判断A股交易状态的python脚本

最近在做股票数据相关的项目&#xff0c;需要用到判断某一天某个时刻A股的状态&#xff0c;比如休市&#xff0c;收盘&#xff0c;交易中等&#xff0c;发动脑筋想了一下&#xff0c;这个其实还是比较简单的&#xff0c;这里我把实现方法分享给大家。 思路 当天是否休市 对于某…

闪记(FlashNote):让灵感快速成文的轻量级笔记工具

闪记&#xff08;FlashNote&#xff09;&#xff1a;让灵感快速成文的轻量级笔记工具 你是否经常遇到这样的情况&#xff1a;桌面上放了一大堆的新建123.txt&#xff0c;想记录一个想法&#xff0c;应该是一键开个一个快捷键然后瞬间记录就自动保存了&#xff0c;现在的很多笔记…

《大模型部署》——ollama下载及大模型本地部署(详细快速部署)

ollama Ollama 是一款开源跨平台的大语言模型&#xff08;LLM&#xff09;运行工具&#xff0c;旨在简化本地部署和管理 AI 模型的流程。 下载ollama 进入官网下载https://ollama.com/ 选择需要的系统下载 下载完成后直接进行安装 下载大模型 选择想要部署的模型&#…

Geotools结合SLD实现矢量中文标注下的乱码和可用字体解析

目录 前言 一、需求溯源 1、原始的SLD渲染 2、最初的效果 二、问题修复 1、还是字符编码 2、如何选择可用的字体 3、如何查看支持的字体库 三、总结 前言 随着地理信息系统&#xff08;GIS&#xff09;技术的不断发展&#xff0c;矢量数据的可视化和标注成为了地理信息展…

基于Python与CATIA V5的斐波那契螺旋线自动化建模技术解析

引言 斐波那契螺旋线&#xff08;Fibonacci Spiral&#xff09;作为自然界广泛存在的黄金比例曲线&#xff0c;在工业设计、产品造型、机械工程等领域具有重要应用价值。本文将以Python控制CATIA V5进行参数化建模为例&#xff0c;深入解析三维CAD环境中复杂数学曲线的自动化生…

动态规划(11.按摩师)

题目链接&#xff1a;面试题 17.16. 按摩师 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a; 状态表示&#xff1a; 对于简单的线性 dp &#xff0c;我们可以⽤「经验 题⽬要求」来定义状态表⽰&#xff1a; 以某个位置为结尾&#xff0c;巴拉巴拉&#xff1b;…