C语言:指针(5)

1. sizeof与strlen的对比

1.1 sizeof

sizeof属于是操作符,用于计算变量所占的空间大小,单位为字节。如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。

sizeof只计算数据在内存中所占的空间大小,而不在乎内存中存放的数据类型。

例如:

cpp 复制代码
int main()
{
    int a = 10;
    printf("%d\n",sizeof a);
    printf("%d\n", sizeof(a));
    printf("%d\n", sizeof(int));
    return 0;
}
cpp 复制代码
4
4
4

1.2 strlen

strlen是C语言库函数,其功能为求字符串长度。函数原型如下:

cpp 复制代码
 size_t strlen ( const char * str );

strlen函数会统计指针str指向的那个地址向后直到字符 '\0' 之前的所有字符个数。

但是,strlen函数会一直向后寻找直到找到 '\0' 为止,所以说有越界访问的可能性。

cpp 复制代码
int main()
{
    char arr1[] = "abd";
    char arr2[] = {'a','b','c'};
    printf("%d\n", strlen(arr1));
    printf("%d\n", strlen(arr2));
    return 0;
}
cpp 复制代码
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 一维数组

cpp 复制代码
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;
}

下面为解析:

cpp 复制代码
int main()
{
    int a[] = {1,2,3,4};
    printf("%d\n",sizeof(a));  
    //sizeof中a单独出现,表示整个数组,结果为16
    printf("%d\n",sizeof(a+0));
    //(a+0),这里a表示数组首元素地址,整体属于整型指针,结果为4/8
    printf("%d\n",sizeof(*a));
     //(*a),这里的a表示数组首元素地址,*a表示首元素,类型为整型,结果为4
    printf("%d\n",sizeof(a+1));
    //(a+1),a表示数组首元素地址,与整数1相加,整体属于整型指针,结果为4/8
    printf("%d\n",sizeof(a[1]));
    //a[1],a表示数组首元素地址,利用操作符[1]来访问数组中下标为1的元素,类型为整型,结果为4
    printf("%d\n",sizeof(&a));
    //&a,得到的是整个数组的地址,类型为int(*)[4],结果为4/8
    printf("%d\n",sizeof(*&a));
    //(*&a),操作符*与&抵消,最终为a,表示整个数组,结果为16
    printf("%d\n",sizeof(&a+1));
    //&a+1,&a得到整个数组地址,+1后跳过整个数组,但是其结果类型依旧为 int(*)[4],结果为4/8
    printf("%d\n",sizeof(&a[0]));
    //&a[0],a[0]访问数组中下标为0的元素,即首元素,操作符&得到该元素的地址,类型为整型指针,结果为4/8
    printf("%d\n",sizeof(&a[0]+1));
    //&a[0]+1,&a[0]得到数组首元素地址,+1后跳过一个元素,结果类型为整型指针,结果为4/8
    return 0;
}
cpp 复制代码
16
8
4
8
4
8
16
8
8
8

2.2 字符数组

2.2.1 字符串末尾无 '\0'

代码1:

cpp 复制代码
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;
}

解析:

cpp 复制代码
int main()
{
    char arr[] = {'a','b','c','d','e','f'};
    printf("%d\n", sizeof(arr));
    //szieof中数组名arr单独出现,表示整个数组,结果为6
    printf("%d\n", sizeof(arr+0));
    //arr+0,表示数组首元素地址,类型为字符指针,结果为4/8
    printf("%d\n", sizeof(*arr));
    //*arr,arr表示数组首元素地址,*arr表示首元素,类型为字符,结果为1
    printf("%d\n", sizeof(arr[1]));
    //arr[1],表示数组中下标为1的元素,类型为字符,结果为1
    printf("%d\n", sizeof(&arr));
    //&arr,&arr得到整个数组的地址,类型为 char(*)[6],结果为4/8
    printf("%d\n", sizeof(&arr+1));
    //&arr+1,&arr得到整个数组的地址,+1跳过整个数组,但结果类型依旧为为char(*)[6],结果为4/8
    printf("%d\n", sizeof(&arr[0]+1));
    //&arr[0]+1,&arr[0]得到首元素的地址,+1后向后跳过一个元素,结果类型为字符指针,结果为4/8
    return 0;
}
cpp 复制代码
6
8
1
1
8
8
8

代码2:

cpp 复制代码
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;
}

解析:

cpp 复制代码
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;
}

将编译错误的语句注释之后得到如下的打印结果:

cpp 复制代码
11
11
11
5
10

2.2.2 字符串末尾存在 '\0'

代码3:

cpp 复制代码
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;
}

解析:

cpp 复制代码
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;
}
cpp 复制代码
7
8
1
1
8
8
8

代码4:

cpp 复制代码
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;
}

解析:

cpp 复制代码
int main()
{
    char arr[] = "abcdef";
    //这样创建字符串,编译器会自动在字符串末尾加上'\0'
    printf("%d\n", strlen(arr));
    //arr,arr表示数组首元素,字符串末尾中有'\0',结果为6
    printf("%d\n", strlen(arr+0));
    //arr+0,arr表示数组首元素,+0后依旧为数组首元素,字符串末尾中有'\0',结果为6
    printf("%d\n", strlen(*arr));
    //编译器报错,原因同代码2
    printf("%d\n", strlen(arr[1]));
    //编译器报错,原因同代码2
    printf("%d\n", strlen(&arr));
    //&arr,&arr得到整个数组的地址,字符串末尾中有'\0',结果为6
    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后跳过一个元素,字符串末尾存在'\0',结果为5
    return 0;
}

将报错语句注释掉后的打印结果:

cpp 复制代码
6
6
6
5
5

这里,倒数二行的输出结果为5属于是巧合,将字符串中的字符删去两个后,其结果如下:

cpp 复制代码
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;
}
cpp 复制代码
4
4
4
5
3

可以发现,除了倒数第二行打印结果不变外,其余打印结果都发生了变化,并且刚好是原本结果减去2。

2.2.3 用字符型指针存储常量字符串地址

代码5:

cpp 复制代码
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;
}

解析:

cpp 复制代码
int main()
{
    char *p = "abcdef";
    //将字符串首字符地址存储在字符型指针p中
    printf("%d\n", sizeof(p));
    //p在sizeof中单独使用,表示字符串首字符的地址,类型为字符型指针,结果为4/8
    printf("%d\n", sizeof(p+1));
    //p+1,p代表字符串首字符的地址,+1后跳过一个字符,结果类型依旧为字符型指针,结果为4/8
    printf("%d\n", sizeof(*p));
    //*p,p为字符串首字符地址,*p得到首字符,即为a,类型为字符,结果为1
    printf("%d\n", sizeof(p[0]));
    //p[0],编译器处理p[0]时,转化为*(p+0),结果同第三行
    printf("%d\n", sizeof(&p));
    //&p,得到指针p的地址,类型为char**,结果为4/8
    printf("%d\n", sizeof(&p+1));
    //&p,得到指针p的地址,+1后跳过一个char**类型的空间大小即跳过指针p的地址,指向存储指针p处地址后的地址,结果类型依旧为char**,结果为4/8
    printf("%d\n", sizeof(&p[0]+1));
    //&p[0]+1,转化为&*(p+0)+1,*与&抵消,因此结果为p+1,指向字符中第二个字符处,即为存储字符b的地址,结果类型为char*,结果为4/8
    return 0;
}
cpp 复制代码
8
8
1
1
8
8
8

代码6:

cpp 复制代码
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;
}

解析:

cpp 复制代码
int main()
{
    char *p = "abcdef";
    //字符串末尾存在'\0'
    printf("%d\n", strlen(p));
    //p为字符串首字符地址,传递给函数strlen,结果为6
    printf("%d\n", strlen(p+1));
    //p+1,p为字符串首字符地址,+1后跳过一个字符,传递给函数strlen,结果为5
    printf("%d\n", strlen(*p));
    //编译器报错,结果同代码2
    printf("%d\n", strlen(p[0]));
    //编译器报错,结果同代码2
    printf("%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,结果为5
    return 0;
}
cpp 复制代码
6
5
0
5
5

这里的倒数第二行打印结果也属于巧合,可以跳过修改字符串验证:

cpp 复制代码
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;
}
cpp 复制代码
4
3
0
5
3

2.3 二维数组

代码7:

cpp 复制代码
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;
}

解析:

cpp 复制代码
int main()
{
    int a[3][4] = {0};
    //创建一个二维数组并将内部元素全部初始化为0
    printf("%d\n",sizeof(a));
    //sizeof中数组名a单独出现,表示整个数组,结果为48
    printf("%d\n",sizeof(a[0][0]));
    //a[0][0],a[0][0]表示数组中首元素,类型为整型数据,结果为4
    printf("%d\n",sizeof(a[0]));
    //二维数组a中,当sizeof中a[0]单独出现,表示数组中的整个第一行,结果为16
    printf("%d\n",sizeof(a[0]+1));
    //a[0]+1,此处a[0]表示数组第一行首元素地址,+1后跳过一个元素,最终结果类型为整型指针,打印结果为4/8
    printf("%d\n",sizeof(*(a[0]+1)));
    //*(a[0]+1),a[0]+1表示数组中第一行第二个元素的地址,加上*表示该地址处存储的数据,该数据类型为整型,结果为4
    printf("%d\n",sizeof(a+1));
    //a+1,a代表二维数组首行的地址,+1后跳过一整行,表示数组中第二行的地址,结果为4/8
    printf("%d\n",sizeof(*(a+1)));
    //*(a+1),a+1表示数组中第二行的地址,加上*,得到整个第二行,结果为16
    printf("%d\n",sizeof(&a[0]+1));
    //&a[0]+1,&a[0]表示数组中整个第一行的地址,类型为int(*)[4],+1后跳过一整行,但数据类型依旧为int(*)[4],结果为4/8
    printf("%d\n",sizeof(*(&a[0]+1)));
    //&a[0]+1,&a[0]表示数组中整个第一行的地址,类型为int(*)[4],+1后跳过一整行,此时指向第二行地址,加上*后,得到整个第二行,结果为16
    printf("%d\n",sizeof(*a));
    //*a,a表示二维数组首行的地址,加上*后,表示整个数组首行,结果为16
    printf("%d\n",sizeof(a[3]));
    //a[3]表示数组整个第四行,虽然此时属于数组访问越界,但是编译器依旧将其作为数组的行类型处理,结果为16
    return 0;
}
cpp 复制代码
48
4
16
8
4
8
16
8
16
16
16

数组名的意义:

1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。

2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

3. 除此之外所有的数组名都表示首元素的地址。