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

深入理解指针5

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就是\0return 0;
}

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

int main()
{char arr[] = {'a','b','c','d','e'}; //字符数组存的都是字符,所以arr数组内存里没有\0,就会越界访问,一直遇到\0为止char arr2[] = "hello";				//字符数组里存的是字符串,字符串末尾有隐藏的\0printf("%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个字节//结果是48printf("%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+1printf("%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"; //内存里有\0printf("%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//结果是6printf("%zd\n", strlen(&arr + 1));//数组地址+1,指向下一个数组的地址,那从下一个数组的地址开始往后统计,你也不知道什么时候,会遇到\0//随机值printf("%zd\n", strlen(&arr[0] + 1));//取出第一个元素的地址,然后+1,指向第二个元素的地址,从第二个元素的地址开始往后统计,//结果是5return 0;
}
int main()
{char arr[] = "abcdef";//内存有\0printf("%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类型的,在内存中占一个字节//结果为1printf("%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,//结果是6printf("%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// 所以结果为0x100014printf("%p\n", (unsigned long)p + 0x1);//注意这题也有个坑,p不是指针类型了,他强制转换成了无符号整型,那这里p+1就是0x100001printf("%p\n", (unsigned int*)p + 0x1);//这里p是一个无符号整型指针,无符号整型是4个字节,所以p+1跳过4个字节//所以结果是0x100004return 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还是4int* 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 和 2000000return 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,0int* 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;

}


---```cint 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'的地址//打印结果为atreturn 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   第五步:+3printf("%s\n", *cpp[-2] + 3);	//打印"ST"//[]的优先级高于*,先执行cpp[-2],然后再解引用//cpp[-2]=*(cpp-2)//*cpp[-2] + 3 =*( *(cpp-2)) + 3printf("%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)

相关文章:

深入理解指针5

sizeof和strlen的对比 sizeof的功能 **sizeof是**** 操作符****&#xff0c;用来**** 计算****变量或类型或数组所占**** 内存空间大小****&#xff0c;**** 单位是字节&#xff0c;****他不管内存里是什么数据** int main() {printf("%zd\n", sizeof(char));p…...

一文详解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/…...

Ground Truth(真实标注数据):机器学习中的“真相”基准

Ground Truth&#xff1a;机器学习中的“真相”基准 文章目录 Ground Truth&#xff1a;机器学习中的“真相”基准引言什么是Ground Truth&#xff1f;Ground Truth的重要性1. 模型训练的基础2. 模型评估的标准3. 模型改进的指导 获取Ground Truth的方法1. 人工标注2. 众包标注…...

双重token自动续期解决方案

Token自动续期实现方案详解 Token自动续期是提升用户体验和保障系统安全的关键机制&#xff0c;其核心在于无感刷新和安全可控。以下从原理、实现方案、安全措施和最佳实践四个维度展开说明&#xff1a; 一、核心原理&#xff1a;双Token机制 Token自动续期通常采用 Access …...

我与数学建模之启程

下面的时间线就是从我的大二上开始 9月开学就迎来了本科阶段最重要的数学建模竞赛——国赛&#xff0c;这个比赛一般是在9月的第二周开始。 2021年国赛是我第一次参加国赛&#xff0c;在报名前我还在纠结队友&#xff0c;后来经学长推荐找了另外两个学长。其实第一次国赛没啥…...

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

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

烧结银:解锁金刚石超强散热潜力​

烧结银&#xff1a;解锁金刚石超强散热潜力​ 在材料科学与热管理领域&#xff0c;金刚石凭借超高的热导率&#xff0c;被誉为 “散热之王”&#xff0c;然而&#xff0c;受限于其特殊的性质&#xff0c;金刚石在实际应用中难以充分发挥散热优势。而烧结银AS9335的出现&#x…...

【蓝桥杯】第十四届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登录到阿里云服务…...

力扣经典算法篇-5-多数元素(哈希统计,排序,摩尔投票法)

题干&#xff1a; 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 示例 1&#xff1a; 输入&#xff1a;nums [3,2,3] 输出&…...

axios介绍以及配置

Axios 是一个基于 Promise 的 HTTP 客户端&#xff0c;用于浏览器和 Node.js 环境中进行 HTTP 请求。 一、特点与基本用法 1.特点 浏览器兼容性好&#xff1a;能在多种现代浏览器中使用&#xff0c;包括 Chrome、Firefox、Safari 等。支持 Promise API&#xff1a;基于 Prom…...

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

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

大数据技术之Scala:特性、应用与生态系统

摘要 Scala 作为一门融合面向对象编程与函数式编程范式的编程语言&#xff0c;在大数据领域展现出独特优势。本文深入探讨 Scala 的核心特性&#xff0c;如函数式编程特性、类型系统以及与 Java 的兼容性等。同时&#xff0c;阐述其在大数据处理框架&#xff08;如 Apache Spa…...

程序化广告行业(47/89):竞价指标剖析与流量对接要点

程序化广告行业&#xff08;47/89&#xff09;&#xff1a;竞价指标剖析与流量对接要点 大家好&#xff01;一直以来&#xff0c;我都希望能和大家一同深入探索程序化广告行业的奥秘&#xff0c;这也是我持续撰写这一系列博客的动力。今天&#xff0c;咱们接着来剖析程序化广告…...

dfs记忆化搜索刷题 + 总结

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

vue2 全局封装axios统一管理api

在vue项目中&#xff0c;经常会使用到axios来与后台进行数据交互&#xff0c;axios丰富的api满足我们基本的需求。但是对于项目而言&#xff0c;每次都需要对异常进行捕获或者处理的话&#xff0c;代码会很繁重冗余。我们需要将其公共部分封装起来&#xff0c;比如异常处理&…...

大模型有哪些算法

大模型&#xff08;Large-scale Models&#xff09;通常指参数量大、架构复杂、在特定任务或领域表现出色的深度学习模型。这些模型的算法核心往往基于Transformer 架构及其变体&#xff0c;同时结合了大规模数据、硬件加速和优化技巧。以下是当前主流大模型及其核心算法的分类…...

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

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

Python Cookbook-4.17 字典的并集与交集

任务 给定两个字典&#xff0c;需要找到两个字典都包含的键(交集)&#xff0c;或者同时属于两个字典的键(并集)。 解决方案 有时&#xff0c;尤其是在Python2.3中&#xff0c;你会发现对字典的使用完全是对集合的一种具体化的体现。在这个要求中&#xff0c;只需要考虑键&am…...

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

专栏&#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借阅图书.....…...

命令模式介绍及应用案例

命令模式介绍 命令模式&#xff08;Command Pattern&#xff09; 是一种行为设计模式&#xff0c;它将请求封装为一个对象&#xff0c;从而使你可以用不同的请求对客户进行参数化&#xff0c;并且支持请求的排队、记录日志、撤销操作等功能。命令模式的核心思想是将“请求”封…...

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

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

python笔记之函数

函数初探 python在要写出函数很简单&#xff0c;通过关键字def即可写出&#xff0c;简单示例如下 def add(a, b):return ab 以上即可以定义出一个简单的函数&#xff1a;接收两个变量a和b&#xff0c;返回a和b相加的结果&#xff0c;当然这么说也不全对&#xff0c;原因就是…...

合合信息大模型加速器2.0实测:当AI开始“读心术“与“考古“

凌晨三点的编辑部&#xff0c;我盯着屏幕上密密麻麻的财务报表和如天书般的专利图纸&#xff0c;感觉咖啡因正在大脑中上演"黑凤凰"式崩溃。这时&#xff0c;合合信息的AI助手突然开口&#xff1a;"您需要的是自动关联32个数据表&#xff0c;还是让模型直接生成…...

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

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