C语言基础 9. 指针
文章目录
- C语言基础 9. 指针
- 9.1. &
- 9.2. 指针
- 9.3. 指针的使用
- 9.4. 指针与数组
- 9.5. 指针与const
- 9.6. 指针运算
- 9.7. 动态内存分配
9.1. &
-
运算符&:
-
scanf(“%d”, &i);里的&
-
获得变量的地址, 它的操作数必须是变量
- int i;printf(“%x”, &i);
-
地址的大小是否与int相同取决于编译器和操作系统
- int i; printf(“%p”, &i);
- %p: 让printf()输出地址(一个十六进制的数)
-
-
&不能取的地址:
- 不能对没有地址的东西取地址
- printf(“%p”, &(a + b));
- printf(“%p”, &(a++));
- printf(“%p”, &(++a)); //error: 表达式必须为左值或函数指示符
- 不能对没有地址的东西取地址
-
试试这些&:
-
变量的地址
- printf(“%p\n”, &i);
-
相邻变量的地址
-
相邻变量: 指的是定义在相邻位置的变量, 即int i; int p; int b;, 这三个变量是相邻创建的, 也就意味着它们的地址也是相邻的
-
printf(“%p\n”, &i); //000000BE57CFFB14
-
printf(“%p\n”, &p); //000000BE57CFFB34
-
printf(“%p\n”, &b); //000000BE57CFFB54
- i和p的地址的差值为20byte, p和b的地址的差值为20byte, 按理来说, 它们之间的差值应该是sizeof(i)=4byte, 这是为什么呢?
-
通过上网查询以及对收集到的信息进行的反复试验, 得出了一些结论
- 在32位操作系统上:
- F4-E8=12, 两个相邻变量, Debug模式下, 在int变量的前后各增加了4个字节, 用于存储调试信息, a的后面4个字节, b的前面4个字节, 再加上a本身的4个字节, 刚好是12个字节. 32位上的堆栈内存分配是自上而下, 从大到小, 即F4>E8
int a; //a4 4 4bint b;printf("%p\n", &a);//00D8FDF4printf("%p\n", &b);//00D8FDE8 printf("%d\n", sizeof(a));//4printf("%d\n", sizeof(b));//4printf("%d\n", sizeof(&a));//4printf("%d\n", sizeof(&b));//4
- 在64位操作系统上:
- 904-8E4=20, 在64位上面, 变量的地址内存变成了8个字节, 同时在编译器的Debug模式下, 存放调式信息的大小也变成了8个字节, a后面8个字节, b前面8个字节, 再加上a本身的4个字节, 刚好是20个字节. 64位的堆栈内存分配是从小到大8E4<904
- 904-8E4=20, 在64位上面, 变量的地址内存变成了8个字节, 同时在编译器的Debug模式下, 存放调式信息的大小也变成了8个字节, a后面8个字节, b前面8个字节, 再加上a本身的4个字节, 刚好是20个字节. 64位的堆栈内存分配是从小到大8E4<904
int a; //a8 4 8bint b;printf("%p\n", &a);//0000006CC111F8E4printf("%p\n", &b);//0000006CC111F904 printf("%d\n", sizeof(a));//4printf("%d\n", sizeof(b));//4printf("%d\n", sizeof(&a));//8printf("%d\n", sizeof(&b));//8
-
更改32位或64位, 配置管理器当中的两个选项都要改成64或者32
-
在64位操作系统上不使用Debug模式, 而是使用Release模式:
-
Debug模式是为了存储调试信息用的, 便于程序员调试错误, 改进代码
-
Release模式是相当于发行版本, 是让用户直接使用的, 所以里面不存放调试信息, 所以两个相邻变量之间的内存相差就等于这两个变量的类型的大小, 也就是sizeof(a)
-
更改Debug和Release模式
-
- 在32位操作系统上:
-
-
&的结果的sizeof
- printf(“%lu\n”, sizeof(&i));
-
数组的地址
int arr[10];
printf(“%p\n”, &arr);
printf(“%p\n”, arr);
-
数组单元的地址
- printf(“%p\n”, &arr[0]);
-
相邻的数组单元的地址
printf(“%p\n”, &arr[0]);//0000000FF50FF940
printf(“%p\n”, &arr[1]);//0000000FF50FF944- 相差了一个sizeof(int)
- 通过测试数组的地址得出的结论: &arr = arr = &arr[0]
-
-
用于测试的代码:
#include <stdio.h>int main() {//int i = 0; //int p = 0;//int b = 1;//printf("%p\n", &i);//printf("%p\n", &p);//printf("%p\n", &b);//printf("0x%x\n", &i);//printf("%p\n", &i);//printf("%lu\n", sizeof(int));//printf("%lu\n", sizeof(&i));//printf("%lu\n", sizeof(&p));///*int a = 1;//int b = 2;//printf("%p", &(a + b));//printf("%p", &(a++));//printf("%p", &(++a));*///int arr[10];printf("%p\n", &arr);//0000000FF50FF940printf("%p\n", arr);//0000000FF50FF940printf("%p\n", &arr[0]);//0000000FF50FF940printf("%p\n", &arr[1]);//0000000FF50FF944return 0;
}
9.2. 指针
-
经典语录:
- 学计算机一定要有一个非常强大的心理状态, 什么呢? 计算机的所有东西都是人做出来的, 别人能想得出来的, 我也一定能想得出来. 在计算机里头没有任何的黑魔法, 所有的东西只不过是我现在不知道而已, 总有一天我会把所有的细节, 所有的内容的东西全部搞明白了.
-
scanf:
-
scanf()是一个函数, 在它当中有办法拿我们传进去的地址, 把它从标准输入里面分析出来的整数, 放到我们所指定的那个变量里面去
-
如果能够将取得的变量的地址传递给一个函数, 能否通过这个地址在那个函数内访问这个变量?
- scanf(“%d”, &i);
-
scanf()的原型应该是怎么样的? 我们需要一个参数能保存别的变量的地址, 如何表达能够保存地址的变量? 如果把它交给一个整数, 有时候整数和地址不见得是相同的类型, 那么什么样的类型可以接收取地址得到的地址呢?
-
-
指针:
- 保存地址的变量
int i = 1;
int* p = &i;//p存放了i的地址, 表示p这个指针指向了变量i
int* p, q;//p表示指针, q表示int
int *p, q;
- 保存地址的变量
-
指针变量:
- 普通变量的值是实际的值
- 指针变量的值是具有实际值的变量的地址
-
作为参数的指针:
- void f(int* p);
- 在被调用的时候得到了某个变量的地址
- int i = 0;
- f(&i);
- 在函数里面可以通过这个指针访问外面的这个i
-
访问那个地址上的变量*:
*
: 是一个单目运算符, 用来访问指针的值所表示的地址上的变量*
: 可以做右值也可以做左值- int k = *p;
- *p = k + 1;
-
传入地址:
- int i;
- scanf(“%d”, i);
- 编译没有报错, 但是执行出错了, 为什么?
- scanf()以为你传进去的是一个地址, 但是你传进去的是一个整数, 然后它用这个整数来做事情, 所以编译不一定出错, 但是运行一定会报错, 因为scanf()把读到的整数写到了不该写的地方
#include <stdio.h>void f(int* p);
void g(int k);int main() {/*int i = 1;int* p = &i;int* p, q;int *p, q;printf("%p\n", p);*/int i = 0;printf("&i = %p\n", &i);f(&i);g(i);printf("i = %d\n", i);return 0;
}void f(int* p) {printf("*p = %d\n", *p);*p = 2;printf("*p = %d\n", *p);
}void g(int k) {printf("k = %d\n", k);
}
9.3. 指针的使用
-
指针应用场景一:
- 交换两个变量的值swap()函数
-
场景二:
- 函数返回多个值, 某些值就只能通过指针返回
- 传入的参数实际上是需要保存带回的结果的变量
- 找出数组中的最大最小值minmax()函数
- 函数返回多个值, 某些值就只能通过指针返回
-
场景三:
-
函数返回运算状态, 结果通过指针返回
-
常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错:
- -1或0(在文件操作会看到大量的例子)
-
但是当任何数值都是有效的可能结果时, 就得分开返回
- 后续的语言(C++, Java)采用了异常处理机制来解决这个问题
-
两个整数做除法divide()函数, 如果除法成功, 返回1, 否则返回0
-
-
野指针(指针最常见的错误):
- 定义了指针变量, 还没有指向任何变量, 就开始使用指针, 这样会导致程序崩溃
- int* p;
- *p = 1;
- 定义了指针变量, 还没有指向任何变量, 就开始使用指针, 这样会导致程序崩溃
-
swap函数, 交换两个变量的值
#include <stdio.h>void swap(int* pa, int* pb);int main() {int a = 1;int b = 2;swap(&a, &b);printf("a = %d\n", a);printf("b = %d\n", b);return 0;
}void swap(int* pa, int* pb) {int tmp = *pa;*pa = *pb;*pb = tmp;
}
- minmax函数, 找出数组中的最大最小值
#include <stdio.h>void minmax(int a[], int length, int* min, int* max);int main(void) {int a[] = { 1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55, };int min, max;int length = sizeof(a) / sizeof(a[0]);minmax(a, length, &min, &max);printf("min = %d, max = %d\n", min, max);return 0;
}void minmax(int a[], int length, int* min, int* max) {*min = *max = a[0];for (int i = 1; i < length; i++) {if (a[i] < *min) {*min = a[i];}else if (a[i] > *max) {*max = a[i];}}
}
- 两个整数做除法divide()函数, 如果除法成功, 返回1, 否则返回0
#include <stdio.h>int divide(int a, int b, int* result);int main(void) {int a = 5;int b = 2;int c;if (divide(a, b, &c) == 1) {printf("%d / %d = %d\n", a, b, c);}return 0;
}int divide(int a, int b, int* result) {int ret = 1;if (a == 0) ret = 0;else {*result = a / b;}return ret;
}
- 野指针
#include <stdio.h>int main(void) {int k;int* p;k = 0;*p = 1;return 0;
}
9.4. 指针与数组
-
传入函数的数组变成了什么?
- 函数参数表中的数组实际上是指针
- sizeof(a) == sizeof(int*)
- 但是可以用数组的运算符[]进行运算
- 函数参数表中的数组实际上是指针
-
数组参数:
- 以下四种参数原型是等价的:
- int sum(int* arr, int n);
int sum(int* , int );
int sum(int arr[], int n);
int sum(int [], int );
- int sum(int* arr, int n);
- 以下四种参数原型是等价的:
-
数组变量是特殊的指针:
-
数组变量本身表达地址, 所以
- int a[10];
int* p = a; //无需用&取地址
但是数组的单元表达的是变量, 需要用&取地址
&a[0], &a[1]
a == &a[0]
- int a[10];
-
[]
运算符可以对数组做, 也可以对指针做
p[0] <==> a[0] -
*
运算符可以对指针做, 也可以对数组做
*a = 25; -
数组变量是const的指针, 所以不能被赋值
int a[] <==> int* const a =…
-
-
用于测试的代码:
#include <stdio.h>void minmax(int a[], int length, int* min, int* max);int main(void) {int a[] = { 1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55, };int min, max;printf("main sizeof(a) = %lu\n", sizeof(a));printf("main a = %p\n", a);int length = sizeof(a) / sizeof(a[0]);minmax(a, length, &min, &max);printf("min = %d, max = %d\n", min, max);printf("a[0] = %d\n", a[0]);return 0;
}void minmax(int a[], int length, int* min, int* max) {printf("minmax sizeof(a) = %lu\n", sizeof(a));printf("minmax a = %p\n", a);a[0] = 1000;*min = *max = a[0];for (int i = 1; i < length; i++) {if (a[i] < *min) {*min = a[i];}else if (a[i] > *max) {*max = a[i];}}
}
9.5. 指针与const
-
指针与const:
- 指针可以是const, 它所指的那个变量也可以是const
-
如果指针是const:
- 表示一旦得到了某个变量的地址, 不能再指向其他变量
- int* const q = &i;//q是const
*q = 26;
q++; //error
- int* const q = &i;//q是const
- 表示一旦得到了某个变量的地址, 不能再指向其他变量
-
如果它所指的变量是const:
- 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
- int i, j;
const int* p = &i;
*p = 26;//error //*p是const
i = 26;
p = &j;
- int i, j;
- 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
-
这是啥意思?
-
const int* p
int const* p //上面两个是一样的
int* const p -
判断哪个被const了的标志是const在的前面还是后面, 如果const在前面则*p是const, 如果是在后面则p是const
-
-
转换:
-
总是可以把一个非const的值转换成const的
void f(const int* x);int a = 15;f(&a);const int b = a;f(&b);b = a + 1; //error
-
当要传递的参数的类型比地址大的时候, 这是常用的手段, 这样既能用比较少的字节数传递值给参数, 又能避免函数对外面的变量的修改
-
-
const数组:
- const int a[] = { 1,2,3,4,5,6 };
- 数组变量已经是const的指针了, 这里的const表明数组的每个单元都是const int
- 所以必须通过初始化进行赋值
-
保护数组值:
-
因为把数组传入函数时传递的是地址, 所以那个函数内部可以修改数组的值
-
为了保护数组不被函数破环, 可以设置参数为const
- int sum(const int a[], int length);
-
#include <stdio.h>int main() {//int i = 1;//int* const q = &i;//q是const//printf("*q = %d\n", *q);// q++//*q = 26;//printf("*q = %d\n", *q);//int i, j;//const int* p = &i;*p = 26;//error //*p是const//i = 26;//p = &j;void f(const int* x);int a = 15;f(&a);const int b = a;f(&b);//b = a + 1; //errorreturn 0;
}
9.6. 指针运算
-
指针 + 1:
-
是在地址值上面+sizeof(指针所指的变量的类型), 而不是直接+1
-
表示要让指针指向下一个变量
-
int a[10];
int* p = a; -
*p -> a[0]
*(p + 1) == a[1];
通项公式: *(p + n) == a[n];
-
-
如果指针不是指向一片连续分配的空间, 如数组, 则这种运算没有任何意义
-
-
指针计算:
- 给指针加减一个整数(+, +=, -, -=)
- 递增递减(++, --)
- 两个指针相减
- 两个地址的差除以sizeof(指针所指变量的类型), 而不是两个地址的差
- 不能做乘除, 没意义
-
*p++
:-
取出p所指的那个数据来, 完事后顺便把p移到下一个位置去
-
*
的优先级虽然高, 但是没有++高 -
常用于数组类的连续空间操作
-
在某些CPU上, 这可以直接被翻译成一条汇编指令
-
可以实现一种新的遍历数组的方法:
char ac[] = { 0,1,2,3,4,5,6,7,8,9,-1 };char* p = ac;while (*p != -1) {printf("%d\n", *p++);}
- -1表示的是数组结尾的标志, 并不会输出
-
-
指针比较:
- <, <=, ==, >, >=, !=都可以对指针做
- 比较它们在内存中的地址
- 数组中的单元的地址肯定是线性递增的
-
0地址:
-
当然你的内存中有0地址, 但是0地址通常是个不能随便碰的地址
-
所以你的指针不应该具有0值
-
因此可以用0地址来表示特殊的事情
- 返回的指针是无效的
- 指针没有被真正初始化(先初始化为0)
-
NULL是一个预定定义的符号, 表示0地址
- 有的编译器不愿意你用0来表示0地址
-
-
指针的类型:
-
无论指向什么类型, 所有的指针的大小都是一样的, 因为都是地址
-
但是指向不同类型的指针是不能直接互相赋值的
- 这是为了避免用错指针
- q = p;
- warning C4133: “=”: 从“char *”到“int *”的类型不兼容
-
-
指针的类型转换:
- void* 表示不知道指向什么东西的指针
- 计算时与char*相同(但不相通)
- 指针也可以转换类型
- int* p = &i;
- void* q = (void*)p;
- 这并没有改变p所指的变量的类型, 而是让后人用不同的眼光通过p看它所指的变量
- 我不再当你是int啦, 我认为你就是个void
- void* 表示不知道指向什么东西的指针
-
用指针来做什么:
- 需要传入较大的数据时用作参数
- 传入数组后对数组做操作
- 函数返回不止一个结果
- 需要用函数来修改不止一个变量
- 动态申请的内存…
//#include <stdio.h>
//
//int main() {
// char ac[] = { 0,1,2,3,4,5,6,7,8,9, };
// char* p = ac;
// printf("p = %p\n", p);//p = 00000054CA5BFBF4
// printf("p + 1 = %p\n", p + 1);//p + 1 = 00000054CA5BFBF5
// printf("*(p + 1) = %d\n", *(p + 1));
// printf("ac[1] = %d\n", ac[1]);
//
// char* p1 = &ac[5];
// printf("p1 - p = %p\n", p1 - p);
//
// int ai[] = { 0,1,2,3,4,5,6,7,8,9, };
// int* q = ai;
// printf("q = %p\n", q);//q = 000000EDD69DF5B4
// printf("q + 1 = %p\n", q + 1);//q + 1 = 000000EDD69DF5B8
// printf("*(q + 1) = %d\n", *(q + 1));
// printf("ai[1] = %d\n", ai[1]);
// /*printf("q - 1 = %p\n", q - 1);*/
// //q += 1;
// printf("q = %p\n", q);
// //printf("++q = %p\n", ++q);
//
//
//
// int* q1 = &ai[6];
// printf("q1 = %p\n", q1);
// printf("q1 - q = %p\n", q1 - q);
//
//
//
// printf("*q++ = %d\n", *q++);
// printf("*q = %d\n", *q);
// return 0;
//}#include <stdio.h>int main(void) {/*char ac[] = { 0,1,2,3,4,5,6,7,8,9,-1 };char* p = ac;while (*p != -1) {printf("%d\n", *p++);}int i[] = {1,2,3,4,};int* q = i;q = p;*/int i;int* p = &i;void* q = (void*)p;return 0;
}
9.7. 动态内存分配
-
输入数据:
-
如果输入数据时, 县高树你个数, 然后再输入, 要记录每个数据
-
C99可以用变量做数组定义的大小, C99之前呢?
-
必须使用动态内存分配
-
int* a = (int*)malloc(n * sizeof(int));//借过来
-
free(a);//还回去
-
-
-
malloc:
-
#include <stdlib.h>
-
void* malloc(size_t size);
-
向malloc申请的空间的大小是以字节为单位的
-
返回的结果时void*, 需要类型转换为自己需要的类型
- (int*)malloc(n * sizeof(int))
-
-
没空间了?
- 如果申请失败则返回0, 或者叫做NULL
- 你的系统能给你多大的空间?
- 分配了35600MB的空间
-
free():
- 把申请得来的空间还给系统
- 申请过的空间, 最终都应该还回去
- 混出来的, 迟早是要还的
-
常见问题:
- 申请了没free->长时间运行内存逐渐下降
- 新手: 忘了
- 老手: 找不到合适的free的时机
- free过了再free
- 地址变过了, 直接去free
- 申请了没free->长时间运行内存逐渐下降
-
经典
- 纸上得来终觉浅, 绝知此事要躬行
#include <stdio.h>
#include <stdlib.h>int main() {//int n;scanf("%d", &n);//int arr[10];///*for (int i = 0; i < n; i++) {// arr[i] = i;// printf("%d ", arr[i]);//}*///int number;//int* a;//int i;//printf("请输入数量: ");//scanf("%d", &number);//a = (int*)malloc(number * sizeof(int));//借过来//for (i = 0; i < number; i++) {// scanf("%d", &a[i]);//}//for (i = 0; i < number; i++) {// printf("%d ", a[i]);//}//free(a);//还回去//系统能给你多大的空间void* p;int count = 0;while (p = malloc(100 * 1024 * 1024)) {count++;}printf("分配了%d00MB的空间\n", count);return 0;
}