1.sizeof和strlen的对比
1.1.sizeof
sizeof 是计算变量所占内存空间大小,单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。sizeof值关注占用内存空间的大小,不在乎内存中放什么数据。
#include <stdio.h>
int main()
{
int a = 10;
printf("%d ", sizeof(a)); //4
printf("%d ", sizeof a); //4
printf("%d ", sizeof(int)); //4
return 0;
}
1.2.strlen
strlen 是C语言库函数,功能是求字符串长度。函数原型如下 :
size_t strlen(const char * str)
统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。 strlen 函数会⼀直向后找 \0 字符,直到找到为止,所以可能存在越界查找。
#include <string.h>
//strlen
int main()
{
char arr1[3] = { 'a','b','c' };
char arr2[] = "abc";//“”最后面还有一个隐藏的\0,{}中没有
printf("%d ", strlen(arr1)); //35 无\0,会越界访问
printf("%d ", strlen(arr2)); //3
printf("%d ", sizeof(arr1)); //3 char类型长度是一个字节
printf("%d ", sizeof(arr2)); //4 “”最后面还有一个隐藏的\0
return 0;
}
1.3.sizeof与strlen对比
sizeof
1.sizeof是操作符
2.sizeof计算的是操作数所占内存的大小,单位是字节
3.不关注内容中存放什么位置
4.sizeof中有表达式的话,表达式不参与计算
strlen
1.strlen是库函数,需要包括头文件string.h
2.strlen是求字符串长度的,统计的是\0之前的字符个数
3.关注内存中是否有\0,如果没有,就会继续往后找,可能会越界
4.strlen的参数是指针,地址
2.数组与指针
2.1.一维数组
数组名是数组首元素的地址,有且仅有两个例外
- sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节
- &数组名,数组名表示整个数组,取出的是整个数组的地址
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a)); //16 int类型4个字节,数组中有四个整形元素
printf("%d\n", sizeof(a + 0)); //4 不是两个例外之一,a表示数组首元素地址,类型是int*,是地址大小就是4/8,32位的平台是4,64位是8
printf("%d\n", sizeof(*a)); //4 a是首元素的地址,*a就是首元素,其类型是int
//*a == a[0] == *(a+0)
printf("%d\n", sizeof(a + 1)); //4 a+1表示数组第二个元素的地址,4/8
printf("%d\n", sizeof(a[1])); //4 a[1]表示数组第二个元素,类型是int,大小4个字节
printf("%d\n", sizeof(&a)); //4 &a是地址,是地址就是4/8
printf("%d\n", sizeof(*&a)); //16 1. *和&相互抵消,得到sizeof(a)
// 2.&a是整个数组的地址,对其解引用访问的是整个数组
printf("%d\n", sizeof(&a + 1)); //4 &a是整个数组的地址,加一跳过整个数组,得该数组后那个位置的地址,4/8
printf("%d\n", sizeof(&a[0])); //4 数组首元素的地址,4/8
printf("%d\n", sizeof(&a[0] + 1));//4 数组第二个元素的地址,4/8
//a - 数组名
//a - 首元素的地址,类型是int*
//&a - 数组的地址,int(*)[4]
2.2.字符数组
代码1:
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr)); //6
printf("%d\n", sizeof(arr + 0)); //4/8 首元素地址,是地址就是4/8个字节
printf("%d\n", sizeof(*arr)); //1 数组首元素
printf("%d\n", sizeof(arr[1])); //1 数组第二个元素
printf("%d\n", sizeof(&arr)); //4/8 整个数组的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr + 1)); //4/8 跳过该数组之后那个位置的地址
printf("%d\n", sizeof(&arr[0] + 1)); //4/8 第二个元素的地址
代码2:
//size_t strlen(const char *s)
//{
//}
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr)); //arr是首元素的地址,数组中没有\0,就会导致越界访问
printf("%d\n", strlen(arr + 0)); //从数组第一个元素开始,数组中没有\0,会越界访问,结果是随机的
printf("%d\n", strlen(*arr)); //会报错,arr是首元素的地址,*a是首元素,就是'a','a'的ASCLL值是97
//相当于把97作为地址传递给了strlen,strlen得到的就是野指针
printf("%d\n", strlen(arr[1])); //arr[1]是'b',也就是把98传给了strlen
printf("%d\n", strlen(&arr)); //&arr是整个数组的地址,起始位置是数组第一个元素的位置,没有\0,会越界访问,随机值x
printf("%d\n", strlen(&arr + 1));//随机值x-6
printf("%d\n", strlen(&arr[0] + 1));//第二个元素的地址,随机值x-1
代码3:
char arr[] = "abcdef";
printf("%d\n", sizeof(arr)); //7 abcdef加上’/0'一共七个字节
printf("%d\n", sizeof(arr + 0)); //4/8 首元素地址,是地址就是4/8个字节
printf("%d\n", sizeof(*arr)); //1 数组首元素
printf("%d\n", sizeof(arr[1])); //1 数组第二个元素
printf("%d\n", sizeof(&arr)); //4/8 整个数组的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr + 1)); //4/8 跳过该数组之后那个位置的地址
printf("%d\n", sizeof(&arr[0] + 1)); //4/8 第二个元素的地址
代码4:
char arr[] = "abcdef";
printf("%d\n", strlen(arr)); //6 arr是首元素的地址,从数组第一个元素算起直到\0停止
printf("%d\n", strlen(arr + 0)); //6 arr+0是首元素的地址,向后在\0之前有6个字符
//printf("%d\n", strlen(*arr)); //'a' - 97,出错
//printf("%d\n", strlen(arr[1])); //出错
printf("%d\n", strlen((const char *)&arr));//6 &arr是数组的地址,
// 起始位置是数组第一个元素的位置
printf("%d\n", strlen((const char*)&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//5 从第二个元素的位置开始计算
return 0;
代码5:
const char* p = "abcdef";
printf("%d\n", sizeof(p)); //4/8 p是指针变量,也就是地址,计算指针变量的大小,4/8个字节
printf("%d\n", sizeof(p+1)); //4/8 p+1是第二个元素的地址
printf("%d\n", sizeof(*p)); //1 p的类型是const char*,*p的类型就是char
printf("%d\n", sizeof(p[0])); //1 首元素
printf("%d\n", sizeof(&p)); //4/8 &p取出p的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&p + 1)); //4/8 跳过该指针变量p之后那个位置的地址
printf("%d\n", sizeof(&p[0] + 1)); //4/8 ‘b'的地址
代码6:
const char* p = "abcdef";
printf("%d\n", strlen(p)); //6 p是首元素的地址,从数组第一个元素算起直到\0停止
printf("%d\n", strlen(p + 1)); //5 p是首元素的地址,+1得到第二个字符的地址
//printf("%d\n", strlen(*p)); //'a' - 97,出错
//printf("%d\n", strlen(p[0])); //出错,p[0]-->*(p+0)-->*p
printf("%d\n", strlen(&p)); //随机值,&p是指针变量p的地址,和字符串"abcdef"关系不大,
//从p这个指针变量的起始位置开始向后数,p变量存放的地址是什么,不知道,所以是随机值
printf("%d\n", strlen(&p + 1));//随机值
printf("%d\n", strlen(&p[0] + 1));//5 p[0]就是*(p+0),&p[0]就是第一个字符的地址
2.3.二维数组
二维数组可以看作是由一维数组组合而成的数组
int a[3][4] = { 0 };
printf("%d\n", sizeof(a)); //48
printf("%d\n", sizeof(a[0][0])); //4 首元素,int类型,4个字节
printf("%d\n", sizeof(a[0])); //16 a[0]是第一行数组名,数组名单独放在sizeof中,计算的是数组的大小
printf("%d\n", sizeof(a[0] + 1)); //4/8 数组名a[0]未单独放在sizeof中,是数组首元素的地址,加1就是a[0][1]的地址
printf("%d\n", sizeof(*(a[0] + 1))); //4 a[0]表示数组首元素的地址,a[0]+1表示第一行第二个元素的地址
printf("%d\n", sizeof(a + 1)); //4/8 a作为数组名并没有单独放在sizeof中,表示首元素的地址,是二维数组的首元素
//是第一行的地址,a+1就是第二行的地址,指向整个第二行,a+1是个数组指针
printf("%d\n", sizeof(*(a + 1))); //16 1.a+1是第二行的地址,进行解引用,*(a+1)就是第二行,计算的是第二行的大小
//2.*(a+1)就是a[1],相当于将a[1]单独放在sizeof中,计算的是第二行的大小
printf("%d\n", sizeof(&a[0] + 1)); //4/8 a[0]是第一行的数组名,&a[0]就是第一行的地址,加一得到第二行的地址,是地址即使4/8个字节
printf("%d\n", sizeof(*(&a[0] + 1))); //16 &a[0]+1是第二行的地址,解引用得到第二行,计算的是第二行的大小
printf("%d\n", sizeof(*a)); //16 a表示二维数组首元素的地址,也就是第一行的地址,*a就是第一行
printf("%d\n", sizeof(a[3])); //16 a[3]无需真的存在,仅仅通过类型就能推断出长度
a[3][4]数组由a[0],a[1],a[2] 三个数组组成。
3.指针运算
代码1:
int main()
{
int a[5] = { 1,2,3,4,5 };
int* p = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(p - 1));//2,5
return 0;
}
p中存储的是数组a之后那个位置的地址,-1得到5的地址,在解引用得到5
代码2:
int main()
{
int a[3][2] = { (0,1),(2,3),(4,5) };
int* p;
p = a[0];
printf("%d", p[0]);//1
return 0;
}
逗号表达式,其运算规则是 “取最后一个表达式的值”。实际上这个数组是{1,3,5}
代码3:
int main() {
int a[5][5];
int(*p)[5]; // 指向包含5个int类型元素的数组的指针
p = a;
printf("%p %d\n", &p[4][3] - &a[4][2], &p[4][3] - &a[4][2]);
return 0;
}
int main()
{
int a[5][5];
int(*p)[4];//p是一个指针数组,指向的数组是四个整形的数组
p = a;//会报错,不能将 "int (*)[5]" 类型的值分配到 "int (*)[4]" 类型的实体
printf("%p %d", &p[4][3] - &a[4][2], &p[4][3] - &a[4][2]);//p[4][2] == *(*(p+4)+2)
//p[4]从数组首地址开始,向后移动四次,每次四个字节,p[4][2]是p[4]再往后移动两字节
return 0;
}
//指针-指针得到指针之间的元素个数
a的类型是int(*)[5],p的类型是int(*)[4].
代码4:
int main()
{
int a[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
int* p1 = (int*)(&a + 1);//&a拿到整个数组的地址,+1得到数组后面那个位置的地址,再-1得到10的地址
int* p2 = *(a + 1);//a是二维数组首元素的地址,也就是第一行的地址,+1得到第二行首元素的地址
//,再-1得到第一行最后一个元素5的地址
printf("%d %d", *(p1 - 1), *(p2 - 1));//10 5
//指针+1加几个字节,取决于指针类型
return 0;
}
代码5:
int main()
{
const char* a[] = { "work","at","alibaba" };//指针数组
const char** pa = a;
pa++;
printf("%s\n", *pa);//at
return 0;
}
代码6:
int main()
{
const char* c[] = { "ENTER","NEW","POINT","FIRST" };
const char** cp[] = { c + 3,c + 2,c + 1,c };
const char*** cpp = cp;
printf("%s\n", **++cpp);//POINT
printf("%s\n", *--*++cpp + 3);//ER
printf("%s\n", *cpp[-2]+3);//ST
printf("%s\n", cpp[-1][-1] + 1);//EW
return 0;
}
(1)cpp中储存的是数组cpp第一个元素的地址,++cpp是cpp中储存到变为数组cp第二个元素的地址,解引用的到数组cp的第二个元素c+2,再解引用得到数组c中的第三个元素“POINT".
(2)++cpp使cpp中储存的变为数组cp中第三个元素的地址,解引用得到数组cp中的第三个元素,即c+1,--(c+1)得到c,即*c+3,对c解引用拿到E的地址,加三得到ER。
(3)cpp[-2]即*(cpp-2),(cpp-2)得到的是数组第一个元素的地址,解引用得到c+3,再解引用得到数组c的第四个元素,加三最后得到ST
(4)cpp[-1][-1]即*(*(cpp-1)-1),*(cpp-1)得到数组c的第三个元素c+2,那*(*(cpp-1)-1)得到的就是c+1的内容,再加1得到EN。
对 指向字符串的指针(如 c[0])进行 +n 操作:偏移字符串内部的字符(在同一个字符串内移动)。
对 指针数组 c 本身(如 c)进行 +n 操作:才会指向数组 c 中的第 n 个元素(如 c + 3 指向 c[3])。