数据类型
- 结构体代码
// 声明1个结构体
typedef struct {
int a;
int b;
} Person;
int main() {
Person *person = malloc(sizeof(Person));
person->a = 11;
person->b = 22;
printf("%d %d \n", person->a, person->b);
return 0;
}
- sizeof 使用
void TestArray(int arr[]) {
printf("TestArray arr size:%llu\n", sizeof(arr));//TestArray arr size:8
}
void test1() {
int arr[] = {10, 20, 30, 40, 50};
printf("array size: %llu\n", sizeof(arr));//array size: 20
//数组名在某些情况下等价于指针
int *pArr = arr;
printf("arr[2]:%d\n", pArr[2]);//arr[2]:30
printf("array size: %llu\n", sizeof(pArr));//array size: 8
//数组做函数函数参数,将退化为指针,在函数内部不再返回数组大小
TestArray(arr);
}
- 修改变量的方式
void test2() {
int a = 10;
a = 20;
printf("直接修改 a:%d\n", a);
int *p = &a;
*p = 30;
printf("间接修改,a:%p %p \n", p ,&a);
printf("间接修改,a:%d\n", a);
}
- 位运算
// ~ 按位取反
// 将每个1变为0,将每个0变为1
unsigned char a = 2; //00000010
unsigned char b = ~a; //11111101
printf("ret = %d\n", a); //ret = 2
printf("ret = %d\n", b); //ret = 253
// 位与 &
// 只有两个操作数的对应位都是1时结果才为1
int c = 10010011;
int d = 00111101;
int e = c & d;
printf("e = %d \n", e);
// C也有一个组合的位与-赋值运算符:&=。下面两个将产生相同的结果:
// val &= 0377
// val = val & 0377
// 位或 |
// 对于每个位,如果操作数中的对应位有一个是1(但不是都是1),那么结果是1.如果都是0或者都是1,则结果位0
// (10010011)
// ^ (00111101)
// = (10101110)
1 指针
1 指针的定义
指针就是变量,内容就是地址,32位机器指针4个字节,64位机器8个字节
int a = 10;
int *p = &a;
// int * 指针类型(**指针类型决定了指针进行解引用操作的时候,能访问空间的大小**)
// p 指针变量
// &a 取地址
int * : *p可以访问4个字节。
char * : *p可以访问1个字节。
double * : *p可以访问8个字节。
2 指针加减
指针加一不是指向下一个紧挨着的地址,是指向下一个指针变量对应的类型变量开始的地址
意义 指针类型决定了:指针走一步走多远(指针的步长)
- 普通类型的指针步长计算
int a = 0;
int *p = &a;
printf("p:%p\n", p);//p:000000000061FDE4
p++;//步长为4个字节
printf("p++:%p\n", p);//p++:000000000061FDE8
- 多级指针的步长计算
int
3 野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
- 1 指针未初始化
- 2 指针越界访问
- 3 指针指向的空间已经释放
避免野指针
- 1 指针初始化
- 2 小心指针越界
- 3 指针指向内存释放 即 指向NULL
- 4 指针只用之前检查有效性
4 指针和数组
void test() {
int c[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
printf("%p\n", c); // 000000000061FDC0
printf("%p\n", &c);// 000000000061FDC0
printf("%p\n", &(c[0]));// 000000000061FDC0
// 数组是特殊指针,上面3个地址一样
int *p = c;
for (int i = 0; i < 9; i++) {
printf("%d \n", *(p + i));
printf("%d \n", c[i]);
printf("%d \n", *(c + i));
}
}
void testArray() {
int a[] = {1, 2, 3, 4, 5, 6};
int *p, *q, *r;
//
p = &a[3];
printf("*p = %d \n", *p);
//
q = a;
q = q + 3;
printf("*q = %d \n", *q);
//
r = a;
printf("*(r+3) = %d \n", *(r + 3));
// 以上3种方式结果一样
}
void test() {
int a = 11;
int b = 22;
int c = 33;
int *p1 = &a;
int *p2 = &b;
int *p3 = &c;
printf("%p %p %p \n", &a, &b, &c); // 000000000061FDD4 000000000061FDD0 000000000061FDCC
printf("%p %p %p \n", p1, p2, p3); // 000000000061FDD4 000000000061FDD0 000000000061FDCC
int *arr[3] = {p1, p2, p3};
printf("%d \n", *arr[0]); //11 取第一个元素对应的值
printf("%d \n", *arr); // 取aar指针对应的地址
printf("%d \n", **arr); //11取aar指针对应的地址的值
}
- 指针数组
指针数组就是存放指针的数组,实际上还是数组,里面存放着不同类型的指针
void test() {
char *arr[] = {"abcdef", "ghi", "jklmn"};
int len = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < len; i++) {
printf("%s\n", arr[i]);
}
printf("%d %d %d \n", sizeof(arr), sizeof(arr[0]), len);
}
- 数组指针
数组指针—就是指针,该指针指向一个数组
5 指针步长
int main() {
int a = 0;
int *p = &a;// 1 指针自身类型 int * 2 指针指向类型 int(决定步长) 3指针指向的内容
*p = 20;
printf("%d %p %p \n", *p, p, &a);//p2=20 000000000061FE14 000000000061FE14
printf("p=%p \n", p);//000000000061FE14
p++;
printf("p=%p \n", p);//000000000061FE18 指针指向的内容是int,所以步长+4
int **temp = &p;
printf("temp=%p %llu \n", temp, sizeof(int *)); // temp=000000000061FE08
temp++;
printf("temp=%p \n", temp);//000000000061FE10 指针指向的内容是int *(指针类型 8个字节)(16 进制)
int *arr[4] = {1, 2, 3, 4};//指针自身的类型 int *[4] 指针指向的类型 int [4]
printf("temp=%p %llu \n", arr, sizeof(int *)); // temp=000000000061FDE0
int *arr1 = (int *) arr;
arr1++;
printf("temp=%p \n", arr1); // temp=000000000061FDE4
char charp[5] = {'a', 'b', 'c', 'd', 'f'};
char (*p1)[4] = &charp;// 指针自身的类型 char (*)[5] 指针指向的内容是 char ()[]
printf("p1=%p \n", p1);//000000000061FDCB
p1++;
printf("p1=%p \n", p1);//000000000061FDCF
return 0;
}
6 内存分布
在程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、数据区(data)和未初始化数据区(bss)3 个部分(有些人直接把data和bss合起来叫做静态区或全局区)。
变量 : 全局变量,局部变量,静态变量
- 代码区
程序被操作系统加载到内存的时候,所有的可执行代码(程序代码指令、常量字符串等)都加载到
代码区,这块内存在程序运行期间是不变的。代码区是平行的,里面装的就是一堆指令,在程序运行期
间是不能改变的。函数也是代码的一部分,故函数都被放在代码区,包括main函数。
- 静态区
静态区存放程序中所有的全局变量和静态变量
- 栈区
栈(stack)是一种先进后出的内存结构,所有的自动变量、函数形参都存储在栈中,这个动作由编
译器自动完成,我们写程序时不需要考虑。栈区在程序运行期间是可以随时修改的。当一个自动变量超
出其作用域时,自动从栈中弹出。
每个线程都有自己专属的栈;
栈的最大尺寸固定,超出则引起栈溢出;
变量离开作用域后栈上的内存会自动释放。
- 堆区
堆(heap)和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的
顺序。更重要的是堆是一个大容器,它的容量要远远大于栈,这可以解决内存溢出困难。一般比较复杂
的数据类型都是放在堆中。但是在C语言中,堆内存空间的申请和释放需要手动通过代码来完成
内存管理 一般遵循以下三个原则
1. 如果明确知道数据占用多少内存,那么数据量较小时用栈,较大时用堆;
2. 如果不知道数据量大小(可能需要占用较大内存),最好用堆(因为这样保险些);
3. 如果需要动态创建数组,则用堆。
- 动态内存是指在堆上分配的内存
- 静态内存是指在栈上分配的内存
7 二级指针
int twoPoint() {
int va = 200;
printf("%d %p\n", va, &va); // 200 000000000061FDEC
int *p = &va;
printf("%p %p %d\n", p, &p, *p);
// 000000000061FDEC 000000000061FDE0 200
// p 是va的地址
// &p 是指针的地址
// *p 指针地址对应的值
int **q = &p;
printf("%p %p %p %d \n", q, &q, *q, (**q));
//000000000061FDE0 000000000061FDD8 000000000061FDEC 200
// q 是二级指针q对应的值,既指针P的地址(&p)
// &q 是二级指针q的地址
// *q q指针(因为是地址)的地址对应值,
// **q 对(*q)的取值
}
8 指针常量和常量指针
指针常量:指针本身是常量,也即指针存储的地址是常量,不能改变。但是地址对应的内容是可以改变的 常量指针:指向常量的指针,指针地址对应的值是常量,不能被修改,但是指针可以修改
- *const (指针常量,指针在前)
- const * (常量指针,指针在后)
void test() {
// 指针常量
int a = 10;
int *const b = &a;
//b = 0x111; 指针不能被修改
*b = 11;// 指针地址对应的值可以被修改
printf("a=%d\n", a);
// 常量指针
int c = 110;
int d = 120;
int const *p = &c;// const int *p;
p = &d;// 指针可以被修改
// *p = 111;// 值不能被修改
int q = 1;//变量
int const w = 12;// 定义常量
int const *ptr1 = &q;// 常量指针
int *const prt2 = &w;// 指针常量
// 错误,不能把常量的地址赋给指针变量
// int *ptr3 = *w;
// 正确,可以把常量的地址赋给常量指针
int const *ptr4 = &w;
//*ptr1 = 3; //错误,间接引用常量指针不可以修改内存中的数据
//*prt2 = 4; //正确,间接引用指针常量可以修改内存中的数据
//ptr1 = &w; //正确,常量指针可以指向其他变量
//prt2 =&w; //错误,指针常量不可以指向其他变量
const int *const ptr5 = &a;//常量指针常量
// *ptr5=5;//错误,不可以间接引用修改内存数据
// ptr5=&b; //错误,不可以修改指向的对象
}
8 数组指针
int main() {
int a[3][4] = {{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11}};
// 定义数组指针
int (*p)[4] = a;
// 对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [4],
// 那么p+1就前进 4×4 = 16 个字节,p-1就后退 16 个字节,这正好是数组 a 所包含的每个一维数组的长度。也就是说,
// p+1会使得指针指向二维数组的下一行,p-1会使得指针指向数组的上一行。
// p 指向 {0, 1, 2, 3} 首地址,*p 取首地址, *p+2 取第3个元素
printf("%p %p %d \n", p, *p, *(*p + 2));
// *(p+1) 取{4, 5, 6, 7} 首地址
printf("%p %d \n", *(p + 1), *(*(p + 1) + 1));
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
return 0;
}
int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
int (*p2)[5]; //二维数组指针,不能去掉括号
指针数组和二维数组指针有着本质上的区别: 指针数组是一个数组,只是每个元素保存的都是指针,以p1为例,在32位环境下它占用4×5= 20个字节的内存。 二维数组指针是一个指针,它指向一个二维数组,以上面的 p2 为例,它占用 4 个字节的内存。
9 函数指针
int Max(int, int);
int main() {
int (*p)(int, int);// 定义一个函数指针
int a, b, c;
p = Max;
//把函数Max赋给指针变量p, 使p指向Max函数
printf("please enter a and b:");
scanf("%d%d", &a, &b);
c = (*p)(a, b); //通过函数指针调用Max函数
printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
return 0;
}
int Max(int a, int b) {
return (a > b) ? a : b;
}