C语言:指针(5)
1. sizeof与strlen的对比
1.1 sizeof
sizeof属于是操作符,用于计算变量所占的空间大小,单位为字节。如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。
sizeof只计算数据在内存中所占的空间大小,而不在乎内存中存放的数据类型。
例如:
int main()
{int a = 10;printf("%d\n",sizeof a);printf("%d\n", sizeof(a));printf("%d\n", sizeof(int));return 0;
}
4
4
4
1.2 strlen
strlen是C语言库函数,其功能为求字符串长度。函数原型如下:
size_t strlen ( const char * str );
strlen函数会统计指针str指向的那个地址向后直到字符 '\0' 之前的所有字符个数。
但是,strlen函数会一直向后寻找直到找到 '\0' 为止,所以说有越界访问的可能性。
int main()
{char arr1[] = "abd";char arr2[] = {'a','b','c'};printf("%d\n", strlen(arr1));printf("%d\n", strlen(arr2));return 0;
}
3
6
这里在创建第二个字符串时没有加上 '\0' ,所以第二行输出的是一个随机结果。像arr1这样创建字符串,编译器会自动在末尾加上一个 '\0' ,所以第一行的输出结果是确定的。
1.3 sizeof 和strlen的对比
sizeof
1.sizeof是操作符
2.sizeof计算操作数所占内存的大小,单位是字节
3.不关注内存中存放什么数据
strlen
1. strlen是库函数,使用需要包含头文件 <string.h>
2.strlen用于求字符串长度,统计字符串中字符 '\0' 之前字符的个数
3.关注内存中是否存在字符 '\0' ,如果不存在,就会一直向后寻找,可能会越界。
2. 数组和指针题目解析
2.1 一维数组
int main()
{int a[] = {1,2,3,4};printf("%d\n",sizeof(a));printf("%d\n",sizeof(a+0));printf("%d\n",sizeof(*a));printf("%d\n",sizeof(a+1));printf("%d\n",sizeof(a[1]));printf("%d\n",sizeof(&a));printf("%d\n",sizeof(*&a));printf("%d\n",sizeof(&a+1));printf("%d\n",sizeof(&a[0]));printf("%d\n",sizeof(&a[0]+1));return 0;
}
下面为解析:
int main()
{int a[] = {1,2,3,4};printf("%d\n",sizeof(a)); //sizeof中a单独出现,表示整个数组,结果为16printf("%d\n",sizeof(a+0));//(a+0),这里a表示数组首元素地址,整体属于整型指针,结果为4/8printf("%d\n",sizeof(*a));//(*a),这里的a表示数组首元素地址,*a表示首元素,类型为整型,结果为4printf("%d\n",sizeof(a+1));//(a+1),a表示数组首元素地址,与整数1相加,整体属于整型指针,结果为4/8printf("%d\n",sizeof(a[1]));//a[1],a表示数组首元素地址,利用操作符[1]来访问数组中下标为1的元素,类型为整型,结果为4printf("%d\n",sizeof(&a));//&a,得到的是整个数组的地址,类型为int(*)[4],结果为4/8printf("%d\n",sizeof(*&a));//(*&a),操作符*与&抵消,最终为a,表示整个数组,结果为16printf("%d\n",sizeof(&a+1));//&a+1,&a得到整个数组地址,+1后跳过整个数组,但是其结果类型依旧为 int(*)[4],结果为4/8printf("%d\n",sizeof(&a[0]));//&a[0],a[0]访问数组中下标为0的元素,即首元素,操作符&得到该元素的地址,类型为整型指针,结果为4/8printf("%d\n",sizeof(&a[0]+1));//&a[0]+1,&a[0]得到数组首元素地址,+1后跳过一个元素,结果类型为整型指针,结果为4/8return 0;
}
16
8
4
8
4
8
16
8
8
8
2.2 字符数组
2.2.1 字符串末尾无 '\0'
代码1:
int main()
{char arr[] = {'a','b','c','d','e','f'};printf("%d\n", sizeof(arr));printf("%d\n", sizeof(arr+0));printf("%d\n", sizeof(*arr));printf("%d\n", sizeof(arr[1]));printf("%d\n", sizeof(&arr));printf("%d\n", sizeof(&arr+1));printf("%d\n", sizeof(&arr[0]+1));return 0;
}
解析:
int main()
{char arr[] = {'a','b','c','d','e','f'};printf("%d\n", sizeof(arr));//szieof中数组名arr单独出现,表示整个数组,结果为6printf("%d\n", sizeof(arr+0));//arr+0,表示数组首元素地址,类型为字符指针,结果为4/8printf("%d\n", sizeof(*arr));//*arr,arr表示数组首元素地址,*arr表示首元素,类型为字符,结果为1printf("%d\n", sizeof(arr[1]));//arr[1],表示数组中下标为1的元素,类型为字符,结果为1printf("%d\n", sizeof(&arr));//&arr,&arr得到整个数组的地址,类型为 char(*)[6],结果为4/8printf("%d\n", sizeof(&arr+1));//&arr+1,&arr得到整个数组的地址,+1跳过整个数组,但结果类型依旧为为char(*)[6],结果为4/8printf("%d\n", sizeof(&arr[0]+1));//&arr[0]+1,&arr[0]得到首元素的地址,+1后向后跳过一个元素,结果类型为字符指针,结果为4/8return 0;
}
6
8
1
1
8
8
8
代码2:
int main()
{char arr[] = {'a','b','c','d','e','f'};printf("%d\n", strlen(arr));printf("%d\n", strlen(arr+0));printf("%d\n", strlen(*arr));printf("%d\n", strlen(arr[1]));printf("%d\n", strlen(&arr));printf("%d\n", strlen(&arr+1));printf("%d\n", strlen(&arr[0]+1));return 0;
}
解析:
int main()
{char arr[] = {'a','b','c','d','e','f'};printf("%d\n", strlen(arr));//arr表示数组首元素地址,但是数组中没有字符'\0',strlen函数会从arr指向的地址向后一直寻找直到找到'\0',结果为随机值printf("%d\n", strlen(arr+0));//arr表示首元素地址,+0后依旧为首元素地址,结果同第一行结果printf("%d\n", strlen(*arr));//arr为首元素地址,*arr得到首元素字符a,被传递给函数后,a会被转化为其ASCII码值97,并且函数会将97作为地址处理,但是这个地址通常是无法访问的,因此编译时会报错,运行会导致程序崩溃printf("%d\n", strlen(arr[1]));//arr[1],为数组中下标为1的元素,即为b,其结果同第三行printf("%d\n", strlen(&arr));//&arr,&arr得到整个数组的地址,strlen函数会从arr指向的地址向后一直寻找直到找到'\0',结果为随机值printf("%d\n", strlen(&arr+1));//&arr+1,&arr得到整个数组的地址,+1后跳过整个数组,strlen函数向后一直寻找直到找到'\0',结果为随机值 printf("%d\n", strlen(&arr[0]+1));//&arr[0]+1,&arr[0]得到数组首元素的地址,+1跳过一个元素,strlen函数向后一直寻找直到找到'\0',结果为随机值 return 0;
}
将编译错误的语句注释之后得到如下的打印结果:
11
11
11
5
10
2.2.2 字符串末尾存在 '\0'
代码3:
int main()
{char arr[] = "abcdef";printf("%d\n", sizeof(arr));printf("%d\n", sizeof(arr+0));printf("%d\n", sizeof(*arr));printf("%d\n", sizeof(arr[1]));printf("%d\n", sizeof(&arr));printf("%d\n", sizeof(&arr+1));printf("%d\n", sizeof(&arr[0]+1));return 0;
}
解析:
int main()
{char arr[] = "abcdef";//这样创建字符串时,编译器为自动在字符串末尾加上一个'\0'printf("%d\n", sizeof(arr));//sizeof中数组名arr单独出现,表示整个数组,数组中一共有"abcdef"以及末尾的'\0',类型都为字符,因此结果为7//其他的打印不受影响,不过&arr的类型变为了char(*)[7],结果与上面一致,printf("%d\n", sizeof(arr+0));printf("%d\n", sizeof(*arr));printf("%d\n", sizeof(arr[1]));printf("%d\n", sizeof(&arr));printf("%d\n", sizeof(&arr+1));printf("%d\n", sizeof(&arr[0]+1));return 0;
}
7
8
1
1
8
8
8
代码4:
int main()
{char arr[] = "abcdef";printf("%d\n", strlen(arr));printf("%d\n", strlen(arr+0));printf("%d\n", strlen(*arr));printf("%d\n", strlen(arr[1]));printf("%d\n", strlen(&arr));printf("%d\n", strlen(&arr+1));printf("%d\n", strlen(&arr[0]+1));return 0;
}
解析:
int main()
{char arr[] = "abcdef";//这样创建字符串,编译器会自动在字符串末尾加上'\0'printf("%d\n", strlen(arr));//arr,arr表示数组首元素,字符串末尾中有'\0',结果为6printf("%d\n", strlen(arr+0));//arr+0,arr表示数组首元素,+0后依旧为数组首元素,字符串末尾中有'\0',结果为6printf("%d\n", strlen(*arr));//编译器报错,原因同代码2printf("%d\n", strlen(arr[1]));//编译器报错,原因同代码2printf("%d\n", strlen(&arr));//&arr,&arr得到整个数组的地址,字符串末尾中有'\0',结果为6printf("%d\n", strlen(&arr+1));//&arr+1,&arr得到整个数组的地址,+1后跳过整个数组,strlen函数向后一直寻找直到找到'\0',结果为随机值printf("%d\n", strlen(&arr[0]+1));//&arr[0]+1,&arr[0]得到数组首元素的地址,+1后跳过一个元素,字符串末尾存在'\0',结果为5return 0;
}
将报错语句注释掉后的打印结果:
6
6
6
5
5
这里,倒数二行的输出结果为5属于是巧合,将字符串中的字符删去两个后,其结果如下:
int main()
{char arr[] = "abcf";printf("%d\n", strlen(arr));printf("%d\n", strlen(arr+0));printf("%d\n", strlen(&arr));printf("%d\n", strlen(&arr+1));printf("%d\n", strlen(&arr[0]+1));return 0;
}
4
4
4
5
3
可以发现,除了倒数第二行打印结果不变外,其余打印结果都发生了变化,并且刚好是原本结果减去2。
2.2.3 用字符型指针存储常量字符串地址
代码5:
int main()
{char *p = "abcdef";printf("%d\n", sizeof(p));printf("%d\n", sizeof(p+1));printf("%d\n", sizeof(*p));printf("%d\n", sizeof(p[0]));printf("%d\n", sizeof(&p));printf("%d\n", sizeof(&p+1));printf("%d\n", sizeof(&p[0]+1));return 0;
}
解析:
int main()
{char *p = "abcdef";//将字符串首字符地址存储在字符型指针p中printf("%d\n", sizeof(p));//p在sizeof中单独使用,表示字符串首字符的地址,类型为字符型指针,结果为4/8printf("%d\n", sizeof(p+1));//p+1,p代表字符串首字符的地址,+1后跳过一个字符,结果类型依旧为字符型指针,结果为4/8printf("%d\n", sizeof(*p));//*p,p为字符串首字符地址,*p得到首字符,即为a,类型为字符,结果为1printf("%d\n", sizeof(p[0]));//p[0],编译器处理p[0]时,转化为*(p+0),结果同第三行printf("%d\n", sizeof(&p));//&p,得到指针p的地址,类型为char**,结果为4/8printf("%d\n", sizeof(&p+1));//&p,得到指针p的地址,+1后跳过一个char**类型的空间大小即跳过指针p的地址,指向存储指针p处地址后的地址,结果类型依旧为char**,结果为4/8printf("%d\n", sizeof(&p[0]+1));//&p[0]+1,转化为&*(p+0)+1,*与&抵消,因此结果为p+1,指向字符中第二个字符处,即为存储字符b的地址,结果类型为char*,结果为4/8return 0;
}
8
8
1
1
8
8
8
代码6:
int main()
{char *p = "abcdef";printf("%d\n", strlen(p));printf("%d\n", strlen(p+1));printf("%d\n", strlen(*p));printf("%d\n", strlen(p[0]));printf("%d\n", strlen(&p));printf("%d\n", strlen(&p+1));printf("%d\n", strlen(&p[0]+1));return 0;
}
解析:
int main()
{char *p = "abcdef";//字符串末尾存在'\0'printf("%d\n", strlen(p));//p为字符串首字符地址,传递给函数strlen,结果为6printf("%d\n", strlen(p+1));//p+1,p为字符串首字符地址,+1后跳过一个字符,传递给函数strlen,结果为5printf("%d\n", strlen(*p));//编译器报错,结果同代码2printf("%d\n", strlen(p[0]));//编译器报错,结果同代码2printf("%d\n", strlen(&p));//&p,&p得到指针p的地址,地址未知,但肯定不与字符串中任意一个字符重叠,传递给函数strlen后,结果为随机值printf("%d\n", strlen(&p+1));//&p,&p得到指针p的地址,+1后地址依旧未知,但肯定不与字符串中任意一个字符重叠,传递给函数strlen后,结果为随机值printf("%d\n", strlen(&p[0]+1));//&p[0]+1,&p[0]得到字符串首字符地址,+1后跳过一个字符,传递给函数strlen,结果为5return 0;
}
6
5
0
5
5
这里的倒数第二行打印结果也属于巧合,可以跳过修改字符串验证:
int main()
{char *p = "abef";printf("%d\n", strlen(p));printf("%d\n", strlen(p+1));printf("%d\n", strlen(&p));printf("%d\n", strlen(&p+1));printf("%d\n", strlen(&p[0]+1));return 0;
}
4
3
0
5
3
2.3 二维数组
代码7:
int main()
{int a[3][4] = {0};printf("%d\n",sizeof(a));printf("%d\n",sizeof(a[0][0]));printf("%d\n",sizeof(a[0]));printf("%d\n",sizeof(a[0]+1));printf("%d\n",sizeof(*(a[0]+1)));printf("%d\n",sizeof(a+1));printf("%d\n",sizeof(*(a+1)));printf("%d\n",sizeof(&a[0]+1));printf("%d\n",sizeof(*(&a[0]+1)));printf("%d\n",sizeof(*a));printf("%d\n",sizeof(a[3]));return 0;
}
解析:
int main()
{int a[3][4] = {0};//创建一个二维数组并将内部元素全部初始化为0printf("%d\n",sizeof(a));//sizeof中数组名a单独出现,表示整个数组,结果为48printf("%d\n",sizeof(a[0][0]));//a[0][0],a[0][0]表示数组中首元素,类型为整型数据,结果为4printf("%d\n",sizeof(a[0]));//二维数组a中,当sizeof中a[0]单独出现,表示数组中的整个第一行,结果为16printf("%d\n",sizeof(a[0]+1));//a[0]+1,此处a[0]表示数组第一行首元素地址,+1后跳过一个元素,最终结果类型为整型指针,打印结果为4/8printf("%d\n",sizeof(*(a[0]+1)));//*(a[0]+1),a[0]+1表示数组中第一行第二个元素的地址,加上*表示该地址处存储的数据,该数据类型为整型,结果为4printf("%d\n",sizeof(a+1));//a+1,a代表二维数组首行的地址,+1后跳过一整行,表示数组中第二行的地址,结果为4/8printf("%d\n",sizeof(*(a+1)));//*(a+1),a+1表示数组中第二行的地址,加上*,得到整个第二行,结果为16printf("%d\n",sizeof(&a[0]+1));//&a[0]+1,&a[0]表示数组中整个第一行的地址,类型为int(*)[4],+1后跳过一整行,但数据类型依旧为int(*)[4],结果为4/8printf("%d\n",sizeof(*(&a[0]+1)));//&a[0]+1,&a[0]表示数组中整个第一行的地址,类型为int(*)[4],+1后跳过一整行,此时指向第二行地址,加上*后,得到整个第二行,结果为16printf("%d\n",sizeof(*a));//*a,a表示二维数组首行的地址,加上*后,表示整个数组首行,结果为16printf("%d\n",sizeof(a[3]));//a[3]表示数组整个第四行,虽然此时属于数组访问越界,但是编译器依旧将其作为数组的行类型处理,结果为16return 0;
}
48
4
16
8
4
8
16
8
16
16
16
数组名的意义:
1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。