指针
实现变量值的交换
通过函数传参处理交换
#include <stdio.h>
void swap(int a, int b){
a = a + b;
b = a - b;
a = a - b;
printf("swap里:a=%d, b=%d, &a=%p, &b=%p\n", a, b, &a, &b);
}
int main(){
int a = 10;
int b = 20;
swap(a, b);
printf("main里:a=%d, b=%d, &a=%p, &b=%p\n", a, b, &a, &b);
}
执行结果:
PS C:\Users\86176\Desktop\嵌入式\c语言\ccode\class5> gcc .\t1.c -o t1.exe
PS C:\Users\86176\Desktop\嵌入式\c语言\ccode\class5> .\t1.exe
swap里:a=20, b=10, &a=000000CAE35FFBC0, &b=000000CAE35FFBC8
main里:a=10, b=20, &a=000000CAE35FFBFC, &b=000000CAE35FFBF8
❌️上面方法是不行的,因为函数传参实质是值copy,函数中的a b 与 main 里的 a b 已经不是同一个了。这时候就要用到指针
指针是什么
int a; // 定义了一个int类型的变量a。实际上是被系统分配了一个4字节的的内存单元
a = 100; // 将数值100写入到a变量所关联的内存单元中
int b = a; // 从a变量所关联的内存单元中取出数值,写入到b所关联的内存单元中
C语言中,任何一个变量,都是有两层含义的:
- 代表该变量的存储单元(地址) 左值(lvalue)
- 代表该变量的存储单元中的数据(数值) 右值(rvalue)
我们对变量的访问无非就是两种情况:
- 把一个数值写入到变量名关联的内存单元中(write)
- 从变量名关联的内存单元中读出数值(read)
系统将变量名与变量的内存空间的地址进行了关联,访问变量数据时,系统实际上是通过变量的内存地址进行的访问。
可不可以直接通过地址来访问变量? ---> 指针。
指针的概念
地址:系统把内存以一个字节为单位划分成很多份并进行编号,这个编号就是内存地址。C语言中,可以认为指针就是一个有类型的地址。一个变量的首地址,成为该变量的“指针”。它标志这该变量的内容从哪里开始的。
-
指针变量:
- 指针变量也是个变量,但是它是用来保存一个对象的地址的。
-
指针变量的定义:
语法:类型说明符 * 指针变量名{=初始化};
- 类型说明符:该指针所指向的那个内存空间的类型
int a = 10; int * p; // 定义空指针 p = &a; int * x = &a; // 定义一个指向变量a地址的指针 // 称之为:指针x指向了变量a注意:32位处理器中地址都是32位,所以指针变量被分配的内存也是32位(4字节),如果是64位处理器中,指针变量被分配到的内存是64位(8字节)。void * 被称为空指针,也是万能指针。
- 指针类型的作用:
- 指针类型是:在指针变量与整数进行加减时,其实际地址与整数之间变化的倍率关系,从示例可以看出int指针+1后,地址移动了4字节,char指针+1后,地址移动了1字节
#include <stdio.h> int main(){ int a = 10; char b = 'a'; int * pa = &a; char * pb = &b; printf("pa=%p\n", pa); printf("pa+1=%p\n", pa+1); printf("pb=%p\n", pb); printf("pb+1=%p\n", pb+1); return 0; } // 执行结果: pa=00000032D27FFC5C pa+1=00000032D27FFC60 pb=00000032D27FFC5B pb+1=00000032D27FFC5C- 确定指针在运行加减运算的时候的步长是多少
- 确定指针在对空间操作时,每次操作的空间是多大。
- 如何获取地址
- 通过取地址符:&
- &对象名 表示获取该对象的地址
- 有些对象名字本身就是他的地址
- 如:数组、函数名
注意:这种方式获取到的地址,是有类型的,类型是该空间的类型。 这里的地址类型要加*,如 int *, char *
- 通过取地址符:&
- 如何访问指针指向的空间的值
需要用到指向运算符:解运算符:* 语法:*地址 <===> 地址对应的那个变量
注意:*要与乘号相同,注意区分,如 (*p)*2int a = 10; &a // 获取a的地址 *(&a) // 获取a的地址的变量 // *& 可以约去 *(&a) <==> a- 代码:
#include <stdio.h> int main(){ int a = 10; int* p = &a; printf("a=%d, *p=%d\n", a, *p); *p = 20; printf("a=%d, *p=%d\n", a, *p); int b = *p; printf("a=%d, *p=%d, b=%d\n", a, *p, b); printf("&a=%p, p=%p, &b=%p\n", &a, p, &b); } 结果: a=10, *p=10 a=20, *p=20 a=20, *p=20, b=20 &a=000000EFF2BFF7C4, p=000000EFF2BFF7C4, &b=000000EFF2BFF7C0
- 类型说明符:该指针所指向的那个内存空间的类型
交换a/b值的代码(指针)
#include <stdio.h>
void swap(int*, int*);
int main(){
int a = 10, b = 50;
printf("a=%d, b=%d\n", a, b);
swap(&a, &b);
printf("a=%d, b=%d\n", a, b);
return 0;
}
void swap(int* a, int* b){
int t = *a;
*a = *b;
*b = t;
}
指针与数组
数组元素与普通变量一样,也有自己的地址,数组元素间地址是连续的
一维数组与指针的关系
我们可以用一个指针来表示已存在的数组,且数组名就是数组首元素的地址
int a[10];
// 以下这两种写法是一样的,因为在表达式中,a就是&a[0]
int* p = &a[0];
int* p2 = a;
// 下面也是等价的
a[0] = 100;
*p = 100;
// *p ===> *a ==> *(&a[0]) ==> a[0]
// 一维数组,第i项的地址就是a+i(因为a是第0项的地址&a[0], a每次加1就是将地址往后移动1个单位,
// 每个单位的长度取决于a的类型,如a是int* ,每个单位就是4,
// a是char* 每个单位就是1。所以&a[i]就是a+i)
&a[i] ===> &a[0] + i ==> a+i
a[i] ===> *(a+i)
注意:数组名虽然可以作为指针使用,但是作为指针使用时,可以进行加减运算,不能进行赋值运算
二维数组与指针的关系
二维数组可以看做是一个一维数组,只不过它里面的每一项又是一个数组。
int a[3][4];
// 通过上面的一维数组,我们可知
a[i] = *(a+i)
// 所以:(注意:下方的 a[i] 其实相当于a[i]数组的第0项的地址:&a[i][0])
a[i][j] ==> *(a[i]+j) ==> *(*(a+i)+j)
#include <stdio.h>
int main(){
char a[3] = {'a', 'b', 'c'};
char b[2][3] = {{'1','2','3'}, {'4','5','\0'}};
printf("sizeof(a)=%ld, sizeof(a+1)=%ld, *(a+1)=%c\n", sizeof(a), sizeof(a+1), *(a+1));
printf("sizeof(b)=%ld, sizeof(b+1)=%ld, *(b+1)=%s\n", sizeof(b), sizeof(b+1), *(b+1));
return 0;
}
结果:
sizeof(a)=3, sizeof(a+1)=8, *(a+1)=b
sizeof(b)=6, sizeof(b+1)=8, *(b+1)=45
一定要注意:在表达式中,数组a退化为它本身第0项的地址,即 a ==> &a[0];非表达式中,单独一个a并不是&a[0], 而是表示数组本身。
int a[10];
// 非表达式中,这里a就是指数组本身,数组长度为10,所以结果为10
sizeof(a) ==> 10
// 表达式中,这里表达式a+1中,a表示 &a[0], &a[0]+1 就是 &a[1], 是个指针
// 这里指针的长度为8(64位系统是8,32为系统是4)
sizeof(a+1) ==> 8
指针与字符串
在C语言中,并没有内置字符串类型,C语言的字符串是通过char*指针来实现的
// 定义了一个char*类型指针,其值保存为 "ABCDEFG" 的首地址,字符串会保存在.rodata里(只读区/常量区)
char* str1 = "ABCDEFG";
代码:
#include <stdio.h>
int main(){
char* str1 = "ABCDEFGHI";
printf("str1=%s, sizeof(str1)=%ld, *str1=%c\n", str1, sizeof(str1), *str1);
char str2[] = {"ABCDEFGHI"};
printf("str2=%s, sizeof(str2)=%ld, *str2=%c\n", str2, sizeof(str2), *str2);
return 0;
}
结果:
str1=ABCDEFGHI, sizeof(str1)=8, *str1=A
str2=ABCDEFGHI, sizeof(str2)=10, *str2=A
- char* 格式定义字符串:
- 格式:char* str1 = "ABCDEFGHI";
- 这种方式会把字符串的值存储在 .rodata (只读区/常量区)中
- 数据只读,不能更改。
- 字符数组格式定义字符串:
- 格式:char str2[] = {"ABCDEFGHI"};
- 这种方式会把字符串的值存储在“栈”中。并且会多存储一个字符 \0,这是字符串的结尾标识符
NULL指针和野指针
NULL指针
NULL指针也叫空指针,其实是系统定义的第一个宏,表示不指向任何空间
- 语法: 类型说明符 * 指针变量名 = NULL;
char * pointer = NULL; // NULL ===> (void*)0 // NULL指针也是空指针,地址为0的地方,不能被访问。访问会报错 - 作用:防止程序中出现定义了指针却又未初始化时,去访问该指针出现数据异常的情况
int *p; // 上面可替换为: int *p = NULL;
野指针
野指针不是NULL指针,而是指向了一个不知道的地方
- 形成的原因:
- 定义了指针,未进行初始化配置。如:int *p;
- 指针在被free和delete后没有置空(=NULL),让人误认为该指针还是个合法的指针
注意:一定不要定义野指针,如果不知道一个指针指向何处,就让他置为空 NULL
指针常量和常量指针
指针常量
是一个常量,其次才是个指针。他是常量就说明这个指针的地址不能修改。但是他的值可以修改。
- 语法: 类型说明符 * const 指针变量名;
char *const s1; // 就是定义了一个指针常量
最典型的指针常量就是数组名;不能改变它的地址,可以改变他的内容;
char a = 'a';
char b = 'b';
char * pointer1 = &a;
char *const pointer2 = &b; // 定义指针常量
pointer2 = &a; // ❌️pointer2是个指针常量,不能修改指针
总结:指针常量,指针本身的空间只读。指针指向的内容可读可写。
常量指针
是一个指针变量,但是其指向的地址空间是个常量,指针可以修改,但是他的值是常量,不能修改。
- 语法:const 类型说明符 * 指针变量名;
// 下方两种方式一样 const char * str1; char const * str1;
指针常量和常量指针的区别:看const修饰的是谁,修饰的是指针变量名,就是就是指针常量,反之则是常量指针
数组指针和指针数组
数组指针
指向一个数组空间的指针
int a[10];
typeof(a) *p; // 定义了一个指针p,他的类型是typeof(a), 他是个数组指针
===> int[10] *p; // 这种写法不符合C语言语法规范
===> int (*p)[10]; // 这里定义了一个指向拥有10个int类型元素的数组空间的指针。
因为*的结合性比较低,所以要用括号括起来,表示定义的是一个指针 注意:二维数组名就是一个数组指针,因为a=&a[0], 所以数组指针的用法就是二维数组的用法
int a[2][4];
int (*p)[4] = a; // 定义一个数组指针p
p+1 ==> &a[1]
*(p+1) ==> a[1] ==> &a[1][0]
*(*(p+1)+j) ==> *(&a[1][0] + j) ==> a[i][j]
指针数组
指针数组,首先是一个数组,其次他的元素是指针类型
int * p[10]; // 定义了一个数组,其元素有10个,元素类型是 int*类型
main的参数
#include <stdio.h>
/*
argc: 传入参数的数量
argv:传入参数的值(文件名也算)
*/
int main(int argc, const char *argv[]){
for (int i=0; i<argc; i++){
printf("第%d个元素:%s\n", i, argv[i]);
}
return 0;
}
结果:
第0个元素:D:\ccode\class5\main.exe
第1个元素:1
第2个元素:2
第3个元素:3
第4个元素:4
第5个元素:5
第6个元素:6
第7个元素:7
第8个元素:8
二级指针与多级指针
二级指针就是一个指针指向另一个指针
- 指针变量的地址:
int a = 10; int * p = &a; // 一级指针 int ** p2 = &p; // 二级指针,存储的值是个一级指针- 解引用:
**p2 ==> *p ==> a - 指针数组的地址:
- 如果一个数组的元素是指针,那个该数组就是个二级指针,同理,如果一个数组的元素是二级指针,那么该数组就是个三级指针
int a = 2, b = 3, c = 4; int *arr[] = {&a, &b, &c}; // arr的元素是一级指针,所以arr是二级指针 arr[0] ==> int* arr ==> &arr[0] ==> int**
几级指针就用几个*,解引用也要解几次
注意:并不是地址一模一样就是同一个指针,他还有类型区别
int a[2][4];
&a[0] 的地址与 &a 一样,但是他们不是同一个指针,他们的类型不同
&a[0] ==> int (*)[4] 类型
&a ==> int (*)[2][4] 类型
函数指针与指针函数
函数指针
是一个指针,保存函数地址的变量。保存的地址是函数的地址。
- 函数指针的定义:
定义:指向的函数的返回值类型 (*函数指针变量名)(指向函数的形参列表);
int sum(int a, int b){ return a+b; } int (*sum_p)(int, int) = sum; - 函数指针的引用:
因为函数名本身就是函数的地址,所以解不解变量都可以
- (*函数指针变量名)(函数的形参);
- 函数指针的变量名(函数的形参);
int sum(int a, int b){ return a+b; } int (*sum_p)(int, int) = sum; // 赋值sum的时候不需要加参数 // 两种格式写法都可以 (*sum_p)(1, 19); sum_p(1, 19); - 用法:
-
线程池
-
回调函数
- 用户把调用函数的地址作为一个参数传入函数,以便该函数在处理相对应的事件的时候,可以灵活的使用不同的方法
-
指针函数
指针作为一个函数返回值类型的函数
int *sum();
指针作为函数的参数
由于形参不能改变实参(形不改实),被调函数想要传递参数给主函数一般只能return返回。除此之外还有两种方式:
- 访问全局变量
- 指针作为函数的参数(就像上面的swap函数)