深入理解指针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就会计算他的类型
总结:
- sizeof是操作符计算的所占内存大小,单位是字节,不关心内存中存放什么数据
- sizeof操作符的操作数如果是表达式,不会计算表达式的值,编译器会转换成计算类型的大小
- 当sizeof计算变量或数组大小时,括号可以省略,但是计算类型的时候,大小不能省略
- 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;
}
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;
}
总结:
- strlen是库函数,使用是需要包含头文件
**<font style="color:#117CEE;"><string.h></font>**
,统计\0之前的字符串的长度,特别关心内存里有没有\0,因为只有遇到\0才会停止统计 - size_t strlen ( const char * str ), 他的返回类型也是size_t的类型,对应的格式也是%zd,他的参数是cha*类型的指针
数组和指针练习题
除了下面的两个例子外,你见到的任何数组名都是表示数组首元素的地址- sizeof(数组名)——数组名单独放在sizeof里表示计算的是整个数组的大小,而不是计算首元素地址的大小
- &数组名——表示取出整个数组的地址,
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+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个字节
注意!不要被这题的陷阱迷惑了,他是用的圆括号,而不是花括号,这是一个逗号表达式,
逗号表达式按照从左到右的顺序依次计算,整个表达式的值是最后一个表达式的值。
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进制,所以要将二进制的补码转成十六进制
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所指向的地址不会因为其他指针的操作而改变
- char ** 类型的指针执行 +1 操作时,指针会在内存中向后移动一个 char * 类型对象,对吗?
解答:
是的。指针运算和指针所指向的类型有关,在C语言中,指针加上一个整数时,实际移动的字节数是n * sizeof(指针所指向的类型)
相关文章:
深入理解指针5
sizeof和strlen的对比 sizeof的功能 **sizeof是**** 操作符****,用来**** 计算****变量或类型或数组所占**** 内存空间大小****,**** 单位是字节,****他不管内存里是什么数据** int main() {printf("%zd\n", sizeof(char));p…...
常见集合篇(一):算法复杂度分析,从理论到业务场景的深度解析
常见集合篇:算法复杂度分析,从理论到业务场景的深度解析 常见集合篇(一):算法复杂度分析,从理论到业务场景的深度解析一、为什么要进行复杂度分析(一)事后统计法的局限性(二…...
SpringCould微服务架构之Docker(9)
Docker的基本操作之数据卷 容器跟数据耦合的问题: 1、不便于修改:当我们要修改Nginx的html内容时,需要进入容器内部修改,很不方便。 2、数据不可复用:在容器内部 的修改对外是不可见的,所有的修改对新创…...
探索 Gaggol:理解 10^^^100 的宇宙级常数
一、常数概述: Gaggol 是一个极其巨大的数学常数,其数值表示为 10^^^100。这个常数是通过对数字 10 进行超递归幂运算得到的结果。 二、Gaggol 的定义: Gaggol 被定义为 10 的超多层超递归幂,即 10 被连续地提升到自身幂的层次达…...
【C++】STL库_stack_queue 的模拟实现
栈(Stack)、队列(Queue)是C STL中的经典容器适配器 容器适配器特性 不是独立容器,依赖底层容器(deque/vector/list)通过限制基础容器接口实现特定访问模式不支持迭代器操作(无法遍历…...
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加行拖拽排序功能示例13,TableView16_13 键盘辅助拖拽示例
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加行拖拽排序功能示例13,TableView16_13 键…...
Python的概论
免责声明 如有异议请在评论区友好交流,或者私信 内容纯属个人见解,仅供学习参考 如若从事非法行业请勿食用 如有雷同纯属巧合 版权问题请直接联系本人进行删改 前言 提示:: 提示:以下是本篇文章正文内容,…...
AI 数字人短视频数字人口播源码:短视频内容生产的新引擎
在当下信息爆炸的时代,短视频已成为主流的信息传播与娱乐方式之一。在如此庞大的市场需求下,如何高效、创新地生产短视频内容成为了行业关注的焦点。AI 数字人短视频数字人口播源码应运而生,为短视频内容生产带来了全新的变革。 一、行业背…...
数字人训练数据修正解释
数字人训练数据修正和查看 不需要GPU也能运行的DH_live-案例 : I tensorflow/core/util/port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To t…...
VRRP虚拟路由器冗余协议
一、VRRP介绍 VRRP(Virtual Router Redundancy Protocol,虚拟路由器冗余协议)是一种用于提高网络可靠性的协议,旨在通过冗余机制解决默认网关单点故障问题。 核心思想:将多个物理路由器虚拟成一个逻辑路由器…...
微前端 - 以无界为例
一、微前端核心概念 微前端是一种将单体前端应用拆分为多个独立子应用的架构模式,每个子应用可独立开发、部署和运行,具备以下特点: 技术栈无关性:允许主应用和子应用使用不同框架(如 React Vue)。独立部…...
Python与图像处理
目录 一、认识图像 1、图像的构成 2、图像模式 二、处理图像 1、图像缩放 2、图像的旋转和翻转 3、滤镜效果 4、图片剪裁 5、图片素描 6、图片加水印 在众多的Python的第三方的库中,Pillow库是一个强大且方便的库,它能够处理图像,比…...
Lua语言脚本环境配置
参考地址:Lua 教程 | 菜鸟教程 Windows等直接下载安装地址:Lua Binaries 上面也是会跳转下载:https://sourceforge.net/projects/luabinaries/ 下载解压后在“环境变量”中添加路径,添加后如果cmd中还是无法使用lua命令&#x…...
JavaScript的异步编程
目录 目标 实战 回调函数 (Callback) Promise 目标 了解异步编程实现方式。 实战 回调函数 (Callback) 当某个任务完成后,调用回调函数来处理结果。它通常会导致回调地狱,即嵌套多个回调函数,官方不推荐使用。 function fetchData(call…...
Ubuntu里安装Jenkins
【方式1】:下载war包,直接运行,需提前搭建Java环境,要求11或17,不推荐,war包下载地址,将war包上传到服务器,直接使用命令启动 java -jar /data/jenkins/jenkins.war【方式2】&#…...
qt介绍tcp通信
服务器端代码 #include "mainwindow.h" #include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);setWindowTitle("服务器");ui->port->setText("…...
elementui的默认样式修改
今天用element ui ,做了个消息提示,发现提示的位置总是在上面,如图: 可是我想让提示的位置到下面来,该怎么办? 最后还是看了官方的api 原来有个自定义样式属性 customClass 设置下就好了 js代码 css代码 效…...
【Windows】win10系统安装.NET Framework 3.5(包括.NET 2.0和3.0)失败 错误代码:0×80240438
一、.NET3.5(包括.NET 2.0和3.0)安装方式 1.1 联网安装(需要联网,能访问微软,简单,很可能会失败) 1.2 离线安装-救急用(需要操作系统iso镜像文件,复杂,成功几率大) 二、联网安装 通过【控制面板】→【程序】→【程序和功能】→【启用或关闭Windows功能】 下载过程…...
leetcode 53.Maximum Subarray
分治法 //lSum表示[left,right]内以left为左端点的最大子段和 //rSum表示[left,right]内以right为右端点的最大字段和 //iSum表示[left,right]的区间和 int divide_conquer(int* nums,int left,int right,int *lSum,int *rSum,int *iSum){int maxSum;//表示[left,right]内的最…...
手机零售行业的 AI 破局与创新降本实践 | OceanBase DB大咖说
OceanBase《DB 大咖说》第 20 期,我们邀请了九机与九讯云的技术总负责人,李远军,为我们分享手机零售企业如何借力分布式数据库OceanBase,赋能 AI 场景,并通过简化架构实现成本管控上的突破与创新。 李远军于2016年加入…...
基于MCU实现的电机转速精确控制方案:软件设计与实现
本文将详细介绍一篇基于微控制器(MCU)的电机转速精确控制的软件方案。通过采样PWM信号控制和ADC采样技术,结合PID闭环控制算法,实现了电机转速的高效、稳定调节。以下是软件方案流程图,下文将对其进行展开讲解。 原图太…...
【力扣hot100题】(026)合并两个有序链表
可以创建一个新链表记录答案: /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListNode *…...
从代码学习深度学习 - 使用块的网络(VGG)PyTorch版
文章目录 前言一、VGG网络简介1.1 VGG的核心特点1.2 VGG的典型结构1.3 优点与局限性1.4 本文的实现目标二、搭建VGG网络2.1 数据准备2.2 定义VGG块2.3 构建VGG网络2.4 辅助工具2.4.1 计时器和累加器2.4.2 准确率计算2.4.3 可视化工具2.5 训练模型2.6 运行实验总结前言 深度学习…...
程序化广告行业(46/89):竞价结算规则、底价策略与内部排名解析
程序化广告行业(46/89):竞价结算规则、底价策略与内部排名解析 大家好!在之前的几篇博客中,我们已经深入探讨了程序化广告的多个重要方面,从基础概念到实际操作流程。我写这些博客的目的,就是希…...
C/C++ 基础 - 回调函数
目录 前言 回调函数预备知识 函数指针 什么是函数指针 函数指针的语法 如何用函数指针调用函数 函数指针作为函数的参数 函数指针作为函数返回类型 函数指针数组 回调函数 什么是回调函数 为什么要用回调函数 怎么使用回调函数 总结 前言 在写项目的时候&#x…...
【Node.js入门笔记12---npm包】
Node.js入门笔记12 Node.js---npm包一、什么是npm包?二、npm 基础使用三、包管理配置文件(package.json)四、提升下载速度 五、包的分类 Node.js—npm包 一、什么是npm包? 定义 npm(全称 Node Package Manager&#x…...
黑盒测试的正交实验法
背景: 利用因果图法、判定表法可以帮助我们对于输入数据的组合情况进行用例设计,但当输入数据的组合数量巨大时,由于不太可能覆盖到每个输入组合的测试情况,因果图法或判定表法可能就不太适用了,可以采用正交实验法、来合理地减少…...
链表算法的技巧和方法
常用技巧: 1、画图 2、引入虚拟的头节点 3、不要害怕浪费空间,要勇于定义变量,eg:当链表的插入和删除的时候,为了便于结构体指针的连续性,就需要定义一个新的结构体指针,能更加方便; 4、使用快慢…...
Upload-labs 靶场搭建 及一句话木马的原理与运用
1、phpstudy及upload-labs下载 (1)下载phpstudy小皮面板 首先需要软件phpstudy 下载地址 phpStudy下载-phpStudy最新版下载V8.1.1.3 -阔思亮 (2)然后到github网址下载源码压缩包 网址 https://github.com/c0ny1/upload-labs 再…...
基于PX4和Ardupilot固件下自定义MAVLink消息测试(QGroundControl和Mission Planner)
在无人机行业,MAVLink(Micro Air Vehicle Link)协议已经成为了通信的标准协议。MAVLink协议定义了一种轻量级的消息传输格式,广泛应用于PX4和ArduPilot等开源无人机飞控固件中。通过自定义MAVLink消息,我们可以为无人机…...
Maven快速上手
在前面我们学习了许多关于JAVA的知识,从今天这个博客开始我们就要进入到JAVAEE进阶的学习中了,在这里你们可以学到项目是怎么被我们一步步写出来。让我们为之加油吧!!!! 本期讲解: 1.了解Maven…...
【Django】教程-5-ModelForm增删改查+规则校验【正则+钩子函数】
【Django】教程-1-安装创建项目目录结构介绍 【Django】教程-2-前端-目录结构介绍 【Django】教程-3-数据库相关介绍 【Django】教程-4-一个增删改查的Demo 11. ModelForm 11.1 models.py from django.utils import timezone from django.db import models# 数据库ÿ…...
JavaScript中 == 和 === 区别
== 运算符 它是相等运算符,用于比较两个 值 是否相等 ( 如果两个值的类型不同,它会尝试将它们转换为相同的类型,然后再比较。) 示例: console.log(1 == 1); // true,因为1被转换为数字1,所以相等 console.log(1 == true); // true,因为true被转换为数字1,所以相等…...
使用LangChain Agents构建Gradio及Gradio Tools(3)——使用Langchain agents构建Gradio UI
使用LangChain Agents构建Gradio及Gradio Tools(3)——使用Langchain agents构建Gradio UI 本篇摘要16. 使用LangChain Agents构建Gradio及Gradio Tool16.3 使用Langchain agents构建Gradio UI16.3.1 创建代理16.3.2 创建Gradio UI16.3.3 运行demo参考文献本章目录如下: 《使…...
scikit-surprise 智能推荐模块使用说明
目录 1、前言 2、算法 3、数据集 3.1 three built-in datasets are available: 3.2 Load a dataset from a pandas dataframe. 3.3 Load a dataset from a (custom) file. 3.4 Load a dataset where folds (for cross-validation) are predefined by some files. 4、pre…...
基于python开发的邮箱合并群发工具
智能邮件群发系统 一个基于Python和PyQt5开发的智能邮件群发工具,支持Word模板和Excel数据源的自动匹配,具有现代化UI界面和友好的用户体验。 Github项目地址:https://github.com/liugang926/Auto-mail-sent.git dist目录有编译好的exe程序&…...
分治算法之凸包问题
1. 算法思路 基本思想 利用分治策略解决凸包问题主要分为两大步骤: 分解(Divide): 将所有点按照 x 坐标排序,并将点集分为左右两部分。 递归地对左右两部分分别求解凸包。 合并(Conquer/Merge…...
OpenBMC:BmcWeb 处理http请求3 字典树查找节点
OpenBMC:BmcWeb 处理http请求2 查找路由对象-CSDN博客 findRouteByPerMethod实际上是调用了perMethod.trie.find(url);来查找路由对象的 class Trie {struct FindResult{unsigned ruleIndex;std::vector<std::string> params;};FindResult findHelper(const std::string…...
音频进阶学习二十五——脉冲响应不变法实现低通滤波器
文章目录 前言一、脉冲响应不变法1.定义2.模拟系统冲激响应的周期采样3.模拟系统和数字系统的频域响应关系1)S域和Z域的关系2)幅频响应的关系 4.通过有理函数设计滤波器5.总结 二、低通滤波器的设计实例1.给定数字滤波器指标2.转换模拟滤波器指标3.模拟滤…...
Linux中输入输出管理技巧
一、输入输出使用到的系统资源 1、字符设备(Character Devices) 什么是字符设备 字符设备是 Linux 中的一类设备,支持以字符为单位进行数据传输。与块设备不同,字符设备不需要缓 冲区,即数据是逐字节直接传递的。典…...
wireshark抓包工具的使用
下载地址:https://www.wireshark.org/#downloadLink 安装方式,一路next。 使用方式 第一步启动后选择你要抓包的网卡,ipconfig 可以查看你的默认网卡,我的是 以太网 双击进入。 筛选操作(快速筛选方式)…...
javaweb自用笔记:文件上传案例、登录(统一拦截)案例
文件上传 或者说新建一个类配置好信息,然后到aliOssUtils里面用getter、setter方法获取到配置项 登录(统一拦截) 前端要json格式的数据,捕获到异常后前端可以显示错误(对不起,操作失败,请联系管…...
【区块链安全 | 第十七篇】类型之引用类型(一)
文章目录 引用类型数据存储位置分配行为 数组特殊数组:bytes 和 string 类型bytes.concat 和 string.concat 的功能分配 memory 数组数组字面量(Array Literals)二维数组字面量数组成员(Array Members)悬空引用&#x…...
2025国内DevOps新手突围指南:从Gitee零门槛入门到工具链深度对比
对于刚接触DevOps的新手,推荐优先选择Gitee DevOps平台,其次是Jenkins和GitLab。Gitee DevOps作为国内领先的一站式研发效能平台,深度融合代码托管、持续集成/持续交付(CI/CD)、项目协作等功能,不仅界面简洁…...
【C语言】文件操作(2)
一、文件的随机读写 在前面我们学习了文件的顺序读写的函数,那么当我们要读取某个指定位置的内容的时候,是否只能顺序的读取到这个内容?还有在对文件进行输入的时候,需要对指定的位置进行写入,那么此时应该怎么办呢&a…...
将内网的IP地址映射到外网的几种方案
文章目录 1. 背景与目标2. 核心方案选型3. 方案A:路由器端口映射(详细步骤)3.1 前置条件3.2 配置流程3.3 验证访问 4. 方案B:云平台NAT网关配置(以阿里云为例)4.1 前置条件4.2 配置流程4.3 验证访问 5. 方案…...
基于深度学习的图像超分辨率技术研究与实现
一、引言 在数字图像处理领域,图像超分辨率技术一直是一个备受关注的热点话题。随着人们对图像质量要求的不断提高,如何将低分辨率图像提升到高分辨率,同时保持图像的细节和清晰度,成为了一个极具挑战性的问题。传统的图像超分辨率技术主要依赖于插值方法,如双线性插值、双…...
A股复权计算_权息数据整理
目录 前置: 步骤: 1 以通达信为参照 2 从优矿获取所需数据 2.1 股票配股信息 2.2 股票分红信息 2.3 股票拆股信息 3 合并数据,制成权息数据表 权息数据截止20250329.7z 视频 前置: 1 本系列将以 “A股复权计算_” 开头…...
如何进行Prompt调优?
一. 神奇的咒语 在输入prompt前,加入下面这一段“神奇的咒语”,中文或者英文,就能帮你优化提示词。 I want you to become my Expert Prompt Creator. Your goal is to help me craft the best possible prompt for my needs. The prompt yo…...
Git Tag 详解:版本管理与实战指南
文章目录 Git Tag 详解:版本管理与实战指南1. Git Tag 的类型2. Git Tag 的常见操作(1) 创建标签① 创建轻量标签② 创建附注标签③ 给指定的提交打标签 (2) 查看标签(3) 删除标签(4) 推送标签到远程① 推送单个标签② 推送所有标签 (5) 删除远程标签 3. 使用 Tag 的…...