ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入理解指针6 - sizeof和strlen、数组和指针练习、指针运算

深入理解指针6 - sizeof和strlen、数组和指针练习、指针运算 目录1.sizeof和strlen的对比2.数组和指针题练习3.指针运算1.sizeof和strlen的对比1.1区分sizeofstrlen本质运算符库函数头文件不需要头文件需要string.h参数可以是类型变量表达式数组名等必须是char*或const char* 类型的。即参数必须是首字符指针计算内容1.占用的内存字节数包括任何结束符\0或填充字节2.不在乎内存中存放的是什么数据1.字符串长度即从开头到第一个\0的字符个数不包含\02.如果内存中没有 \0 就会持续往后找可能会越界结果类型size_t (无符号整数)单位是字节size_t2.数组和指针题练习回顾数组名的意义1. sizeof(数组名)这⾥的数组名表⽰整个数组计算的是整个数组的⼤⼩。2. 数组名这⾥的数组名表示整个数组取出的是整个数组的地址。3. 除此之外所有的数组名都表示⾸元素的地址1 -一维数组sizeof 与 一维数组int main() { int a[] { 1,2,3,4 }; printf(%zu\n, sizeof(a));//16数组名单独放sizeof()里计算的是整个数组的大小 printf(%zu\n, sizeof(a 0));//4/8a0是首元素的地址地址就是4/8 printf(%zu\n, sizeof(*a));//4, *a a[0] printf(%zu\n, sizeof(a 1));//4/8, a 1 -- a[1] printf(%zu\n, sizeof(a[1]));//4 printf(%zu\n, sizeof(a));//4/8, a是整个数组的地址依然是地址 printf(%zu\n, sizeof(*a));//16 //a -- int (*)[4] //*a -- a //sizeof(*a) sizeof(a) printf(%zu\n, sizeof(a 1));//4/8 a 1还是地址 printf(%zu\n, sizeof(a[0]));//4/8 printf(%zu\n, sizeof(a[0] 1));//4/8 return 0; }总结数组名只在两种情况下不退化为指针作为sizeof的操作数或者作为的操作数如a一旦参与运算如a 0a 1数组名就会退化为指向首元素的指针sizeof作用于指针时结果是指针变量本身的大小32 位 4 字节64 位 8 字节与它指向的空间大小无关2 - 字符数组sizeof 与 字符数组int main() { char arr1[] { a,b,c,d,e,f };//用字符列表初始化 printf(%zu\n, sizeof(arr));//6 printf(%zu\n, sizeof(arr 0));//4/8, arr 0 arr[0] printf(%zu\n, sizeof(*arr));//1, *arr arr[0] printf(%zu\n, sizeof(arr[1]));//1 printf(%zu\n, sizeof(arr));//4/8 -- char(*)[6] printf(%zu\n, sizeof(arr 1));//4/8-- char(*)[6] printf(%zu\n, sizeof(arr[0] 1));//4/8 return 0; }int main() { char arr2[] abcdef;////用字符串初始化 printf(%zu\n, sizeof(arr));//7 printf(%zu\n, sizeof(arr 0));//4/8,地址 printf(%zu\n, sizeof(*arr));//1, arr[0] *arr printf(%zu\n, sizeof(arr[1]));//1 printf(%zu\n, sizeof(arr));//4/8 printf(%zu\n, sizeof(arr 1));//4/8 printf(%zu\n, sizeof(arr[0] 1));//4/8 return 0; }注意第一段代码的数组是用字符初始化的第二段代码的数组是用字符串初始化的strlen 与 字符数组int main() { char arr[] { a,b,c,d,e,f };//用字符列表初始化 printf(%zu\n, strlen(arr));//未知数字符数组没有 \0strlen 会一直越界读取 printf(%zu\n, strlen(arr 0));//未知数 printf(%zu\n, strlen(*arr));//*arr arr[0] a 97, 程序会崩溃strlen的参数必须是地址这里会把97理解为地址访问 printf(%zu\n, strlen(arr[1]));//b-98,程序会崩溃 printf(%zu\n, strlen(arr));//未知数 (没有 \0越界读取结果未知) //char(*)[6]- const char* printf(%zu\n, strlen(arr 1));//未知数(指向 arr 之后的地址。这个地址仍在数组之外从此处开始找 \0 同样是越界读取结果未定义) printf(%zu\n, strlen(arr[0] 1));//未知数 (从 b 开始找 \0依然没有终止符越界读取结果未定义) return 0; }int main() { char arr[] abcdef;//用字符串初始化 printf(%zu\n, strlen(arr));//6 printf(%zu\n, strlen(arr 0));//6 //printf(%zu\n, strlen(*arr));//程序会崩溃 //printf(%zu\n, strlen(arr[1]));//程序会崩溃 printf(%zu\n, strlen(arr));//6 printf(%zu\n, strlen(arr 1));//未知数 printf(%zu\n, strlen(arr[0] 1));//5 return 0; }总结没有\0的字符数组绝不能直接传给strlen会产生越界读取未定义行为得到的结果无意义且可能影响程序稳定性不能把单个字符整数当作地址传给strlen这几乎必然导致程序崩溃sizeof 与 指针int main() { char* p abcdef; printf(%zu\n, sizeof(p));//4/8 printf(%zu\n, sizeof(p 1));//4/8, p1是b的地址 printf(%zu\n, sizeof(*p));//1, *p a printf(%zu\n, sizeof(p[0]));//1 //p[0] *(p0) a printf(%zu\n, sizeof(p));//4/8 printf(%zu\n, sizeof(p 1));//4/8 printf(%zu\n, sizeof(p[0] 1));//4/8, b的地址 return 0; }注意p放的是字符串abcdef的首地址strlen 与 指针int main() { char* p abcdef; printf(%zu\n, strlen(p));//6 printf(%zu\n, strlen(p 1));//5 printf(%zu\n, strlen(*p));//程序崩溃 printf(%zu\n, strlen(p[0]));//程序崩溃 printf(%zu\n, strlen(p));//未知数 p是指针变量p 是 char* 类型的指针变量p 的类型是 char**指向指针的指针。p放的是p的地址不是a的地址 printf(%zu\n, strlen(p 1));//未知数 指向 p 变量之后的内存区域 printf(%zu\n, strlen(p[0] 1));//5 p[0] 1 - b的地址 return 0; }3 - 二维数组sizeof 和 二维数组​ int main() { int a[3][4] { 0 }; printf(%zu\n, sizeof(a));//3*4*448 printf(%zu\n, sizeof(a[0][0]));//4 printf(%zu\n, sizeof(a[0]));//16 a[0]是二维数组的首元素是一个一维数组所以a[0]是第一行的数组名计算的是第一行的大小单位是字节 printf(%zu\n, sizeof(a[0] 1));//4/8, a[0] 1--a[0][1]某个元素的地址 printf(%zu\n, sizeof(a 1));//4/8, a1是第二行的地址 //a[0]1a[0]是一维数组类型是 int* 1跳过一个 int元素所以指向 a[0][1] / a[0]在这里退化为首元素地址再1所以代表的就是a[0][1]的地址 //a1 a 是二维数组这里退化为首元素的的地址即第一行的地址类型是 int(*)[4] ,1跳过一行 printf(%zu\n, sizeof(*(a[0] 1)));//4, *(a[0] 1))-a[0][1] //a- int(*)[4] printf(%zu\n, sizeof(*(a 1)));//16 //*(a1)a[1] printf(%zu\n, sizeof(a[0] 1));//4/8, a[0] 1是第二行的地址a[0]是取出了整个第一行的地址1就是指向第二行 printf(%zu\n, sizeof(*(a[0] 1)));//16 printf(%zu\n, sizeof(*a));//16 printf(%zu\n, sizeof(a[3]));//16, a[3] 看起来像是越界访问但实际上 sizeof 不会对表达式进行实际求值它只关心表达式的类型 //a[3] -- a[0] //int[4] return 0; } ​printf(%zu\n, sizeof(a));//3*4*448 printf(%zu\n, sizeof(a[0]));//16//第一句 a 是数组名计算的是整个数组的大小//第二句 a[0] 是数组名它是二维数组的首元素是一个数组所以 a[0] 是第一行的数组名计算的是第一行的大小单位是字节printf(%zu\n, sizeof(a[0] 1));//4/8 printf(%zu\n, sizeof(a 1));//4/8//第一句 a[0] 1 a[0] 是一维数组这里退化为首元素地址类型是 int* , 1跳过一个int 元素所以指向 a[0][1]//第二句 a1 a 是二维数组这里退化为首元素地址也就是第一行的地址类型是int(*)[4] ,1跳过一行所以 a1 是第二行的地址printf(%zu\n, sizeof(a[0] 1));//4/8a[0] 取出的是整个第一行的地址1就是指向第二行printf(%zu\n, sizeof(a[3]));//16a[3]看起来像越界访问但实际上 sizeof 不会对表达式进行求值它只关心表达式的类型和a[0]、a[1]是一样的3.指针运算例题1int main() { int a[5] { 1, 2, 3, 4, 5 }; int* ptr (int*)(a 1); printf(%d,%d, *(a 1), *(ptr - 1));//25 return 0; }解析如图最终输出 2 和 5例题2下面这段代码在x86环境下假设结构体的大小是20个字节程序的输出结果是啥struct Test { int Num; char* pcName; short sDate; char cha[2]; short sBa[4]; }*p (struct Test*)0x100000; int main() { printf(r1%p\n, p 0x1);//00100014 printf(r2%p\n, (unsigned long)p 0x1);//00100000 printf(r3%p\n, (unsigned int*)p 0x1);//00100004 return 0; }解析0x1就是11 - p 0x1p是个指针变量它的类型是 struct Test*指向的是struct Test类型的的数据指针1会增加 sizeof(struct Test)个字节也就是20个字节所以 p1 就是加了20十六进制是 0x14个字节 , r 0x100014 (以%p打印的时候可能会打印00100014)2 - (unsigned long)p 0x1p被强转为无符号长整型即 p 变成了整数值 0x100000整数加 1 就是普通的加法→ 0x10000010x100000输出 r2 001000003 - (unsigned long*)p 0x1p被强转为 unsigned long* 类型的指针1 跳过4个字节所以输出 r3 00100004例题3int main() { int a[3][2] { (0, 1), (2, 3), (4, 5) }; int* p; p a[0]; printf(%d, p[0]);//1 return 0; }解析注意(0, 1), (2, 3), (4, 5)是逗号表达式并不是在按行初始化逗号操作符会依次计算左右操作数并返回右边操作数的值。所以(0, 1)→ 值为1(2, 3)→ 值为3(4, 5)→ 值为5于是这行初始化等价于 int a[3][2] { 1 , 3 , 5 };//其他元素都是0所以 p[0] *(p0) a[0][0] 1例题4假设环境是x86环境程序输出结果是啥int main() { int a[5][5]; int(*p)[4]; p a; printf(%p,%d\n, p[4][2] - a[4][2], p[4][2] - a[4][2]);//FFFFFFFC,-4 return 0; }解析int a[5][5];// a 的实际行宽为 5 个 int20 字节int(*p)[4];// p 是指向 int[4] 的指针行宽为 4 个 int(16 字节)p a;// p 指向 a 的起始地址但类型不匹配按 p 的类型解析如上图所示指针相减得到的是地址之间的元素个数p[4][2] - a[4][2] -4但是%p将差值-4作为地址输出通常会输出其补码形式FFFFFFFC%d正常输出‑4例题5int main() { int aa[2][5] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int* ptr1 (int*)(aa 1); int* ptr2 (int*)(*(aa 1)); printf(%d,%d, *(ptr1 - 1), *(ptr2 - 1));//105 return 0; }解析如图最终输出105例题6int main() { char* a[] { work,at,alibaba }; char** pa a; pa; printf(%s\n, *pa);//at return 0; }解析如图*pa 得到 a[1]也就是指向 at 的指针%s 打印字符串 at例题7int main() { char* c[] { enter,new,point,first }; char** cp[] { c 3,c 2,c 1,c }; char*** cpp cp; printf(r1 %s\n, **cpp); printf(r2 %s\n, *-- * cpp 3); printf(r3 %s\n, *cpp[-2] 3); printf(r4 %s\n, cpp[-1][-1] 1); return 0; }解析注意操作符的优先级最高 [ ] , 较高 和 -- , 其次 * , 最后 和 - [ ] E1[ E2] *( E1E2); 如cp[ 2 ] *(cp 2) [ ]就相当于解引用printf(r1 %s\n, **cpp); printf(r2 %s\n, *-- * cpp 3); printf(r3 %s\n, *cpp[-2] 3); printf(r4 %s\n, cpp[-1][-1] 1);c四个字符串指针cp元素依次指向 c[3], c[2], c[1], c[0]cpp 初始指向 cp[0]1 - **cppprintf(r1 %s\n, **cpp);如图蓝色的线代表cpp一开始存的地址绿色的线代表**cpp的步骤cppcpp1指向cp[1]*cpp解引用一次*cpp cp[1]即c2指向c[2]**cpp再解引用**cpp *cp[1] p的地址%s 打印字符串输出point注cpp后改变了cpp的值cpp不再指向cp,而是指向cp[1]2 - *--*cpp3printf(r2 %s\n, *-- * cpp 3);按优先级结合* --*cpp3cppcpp 指向 cp[2]*cpp→ cp[2]即 c1指向 c[1]-- * cpp对 cp[2] 做 --变为 c指向 c[0]即cp[2] 被改为 c*(-- * cpp)→ *c → c[0] → enter。最后 3指针偏移 3得到 enter[3] → er。输出er注此时 cpp 指向 cp[2]且cp[2] 已修改为 c3 - *cpp[-2]3printf(r3 %s\n, *cpp[-2] 3);注这句代码中cpp的值没有被修改所以是从cp[2]开始的cpp[-2]即 *(cpp - 2)→ cp[0]仍是 c3未被修改* cpp[-2]→ *cp[0] → c[3] → first3→ first[3] → st输出st cpp 不变仍指向 cp[2]4 - cpp[-1][-1]1printf(r4 %s\n, cpp[-1][-1] 1);注cpp不变扔指向cp[2]cpp[-1]→ *(cpp-1) → cp[1] 指向 c[2]cpp[-1][-1]→ *( *(cpp-1) -1)→ c[1] → new//[ ]就相当于解引用//cpp[-1][-1]也可以写成 *(c2 - 1) → c[1] → new;1→ new[1] → ew输出ew总结数组名退化为指针c 退化为 char**cp 退化为 char***E1[E2] *(E1E2)所有下标都可转化为指针加减负数合法优先级后缀 [ ] 最高前缀 /--/ * 次之算术 最后自增自减会修改原值第二步 -- * cpp 永久改变了 cp[2]字符串偏移对字符指针加 n就是要偏移 n 个字符从那个字符开始算起所以复盘时要注意画清楚初始指针关系图每一步跟踪 cpp 的位置变化用正确的方法分析优先级注意哪些操作造成了副作用自增、自减它们会影响后续语句
返回列表