这里写目录标题
前言
指针是c语言学习的重点,让我们由浅入深,一步步进行了解。
1. int a = 10;
2. char b = 'b';
3. float c = 1.0f;
如上面的代码,每个变量在创建时都会在内存上开辟一个空间,不同类型的变量有不同的大小,每个变量都有自己的起始位置,起始位置就是该变量的地址,C语言中给地址起了新的名字叫做:指针。
这就好比我们出去住酒店,酒店的每一个房间都有一个号码,当我们将房间号告诉朋友并让他储存起来,下一次朋友来找我们,就可以通过储存的房间号,来找到我们,指针也是这样的一个道理,我们通过使用指针将变量的地址储存,在有需要的情况下,我们就可以通过访问指针储存的地址,来找到我们的目标变量,并进行下一步操作。
所以我们可以理解为:内存单元编号 == 地址 == 指针
一、指针的类型
(1)、指针初始化
1. int* p = NULL;
2. char* p = NULL;
3. int** p = NULL;
4. char**p = NULL;
5. int*(*p)[10] = NULL;
6. int(*p)[5] = NULL;
这是几个常见的指针的初始化,下面我们来区分他们。
(2)、指针的类型
将指针变量p去掉,就是指针的类型
1. int* p = NULL;//指针类型为int*
2. char* p = NULL;//指针类型为char*
3. int** p = NULL;//指针类型为int**
4. char**p = NULL;//指针类型为char**
5. int*(*p)[10] = NULL;//指针类型为int*(*)[10]
6. int(*p)[5] = NULL;//指针类型为int(*)[5]
(3)指针指向的类型
同理,去掉指针变量p和与其最近的 ’*’ 号,就可以得到指针所指向的内容
1. int* p = NULL;//指针指向的类型为int
2. char* p = NULL;//指针指向的类型为char
3. int** p = NULL;//指针指向的类型为int*
4. char**p = NULL;//指针指向的类型为char*
5. int*(*p)[10] = NULL;//指针指向的类型为int*()[10]
6. int(*p)[5] = NULL;//指针指向的类型为int()[5]
(4)解引用
代码中的 ‘*’ 号,就是解引用操作符
1. int main()
2. {
3. int a = 100;
4. int* pa = &a;
5. *pa = 0;
6. return 0;
7. }
上⾯代码中第5⾏就使⽤了解引⽤操作符, *pa 的意思就是通过pa中存放的地址,找到指向的空间,pa其实就是a变量了;所以pa = 0,这个操作符是把a改成了0。
二、指针的使用
(1)解引用操作符
代码中的 ‘*’ 号,就是解引用操作符
1. int main()
2. {
3. int a = 100;
4. int* pa = &a;
5. *pa = 0;
6. return 0;
7. }
上⾯代码中第5⾏就使⽤了解引⽤操作符, *pa 的意思就是通过pa中存放的地址,找到指向的空间,pa其实就是a变量了;所以pa = 0,这个操作符是把a改成了0。
(2)、取地址操作符
取地址操作符 —— &
1. int main()
2. {
3. int a = 10;
4. int*p = &a;
5. *p = a;
6. p = &a;
7. printf("%p\n", p);
8. printf("%p\n", p+1);
9. return 0;
10.}
1. 005BF984
2. 005BF988
上述代码中,我们通过取地址操作符 ‘ & ’ 将a的地址存到指针p里面,p+1的地址比p的地址多4,因为&a取出的是a所占4个字节中地址较⼩的字节的地址。在访问时会通过其变量类型来向后访问相应的字节。( int 占四字节 )
三、指针变量的大小
32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。
如果指针变量是⽤来存放地址的,那么指针变的⼤⼩就得是4个字节的空间才可以。
同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要8个字节的空间,指针变量的⼤⼩就是8个字节
1. int main()
2. {
3. printf("%zd\n", sizeof(char *));
4. printf("%zd\n", sizeof(short *));
5. printf("%zd\n", sizeof(int *));
6. printf("%zd\n", sizeof(double *));
7. return 0;
8. }
X86环境下输出
1. 4
2. 4
3. 4
4. 4
X64环境下输出
1. 8
2. 8
3. 8
4. 8
结论:
- 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
- 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
- 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。
四、指针的运算
(1)、指针±整数
数组在内存中是连续存放的
1. int main()
2. {
3. int arr[10] = {1,2,3,4,5,6,7,8,9,10};
4. int *p = &arr[0];
5. int sz = sizeof(arr) / sizeof(arr[0]);
6. for(int i = 0; i < sz; i++)
7. {
8. printf("%d ", *(p + i));
9. }
10. return 0;
11.}
1. 1 2 3 4 5 6 7 8 9 10
*p取出数组首元素的地址,i为整数,(p+i)就会依次取出数组的元素。 同理,也可以计算指针减去一个整数。
(2)指针-指针
指针 - 指针的操作,其本质是计算两个指针之间有多少个元素,虽然在数值上是字节的大小,但默认会根据指针指向内容的类型大小进行计算,即指针指向内容若为 int 类型 ,两个指针相减数值上位40,那么默认会进行 40 ÷ 4 的操作,即指针之间有10个元素。
1. int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
2.int* p1 = &arr[0];
3.int* p2 = &arr[0] + 10;
4.int num = p2 - p1;
5.printf("%d - %d = (数值大小)\n", p2, p1);
6.printf("p2 - p1 = %d\n", num);
1.19922500 - 19922460 = (数值大小)//40
2.p2 - p1 = 10
(3)、指针的运算关系
指针和指针可以用来进行大小的比较,通过比较可以实现一系列功能,例如打印数组。
1.int arr[10] = {0,1,2,3,4,5,6,7,8,9};
2.int* p1 = &arr[0];
3.int* p2 = &arr[0]+10;
4.while(p1 < p2)
5.{
6. printf("%d ",*p1);
7. p1++;
8.}
1.0 1 2 3 4 5 6 7 8 9
五、野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
(1)、野指针成因
a、指针未进行初始化
1. int main()
2. {
3. int *p;//局部变量指针未初始化,默认为随机值
4. *p = 10;
5. return 0;
6. }
b、指针越界访问
1. int main()
2. {
3. int arr[10] = {0};
4. int *p = &arr[0];
5. int i = 0;
6. for(i = 0; i <= 11; i++)
7. {
8. *(p++) = i;//指针指向的范围超出数组arr的范围时,p就是野指针
9. }
10. return 0;
11.}
c、指针所指向的空间被释放
1. int* test()
2.{
3. int n = 100;
4. return &n;
5.}
6.int main()
7.{
8. int* p = test();
9. printf("%d\n", *p);
10. return 0;
11.}
(2)、如何避免野指针
a、指针初始化
b、小心指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
c、指针变量不在使用时,及时置为NULL,指针使用之前检查有效性
d、避免返回局部变量的地址
六、二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥?
这就是 ⼆级指针 。
对于二级指针的运算有:
- *ppa 通过对ppa中的地址进⾏解引⽤,这样找到的是 pa , *ppa 其实访问的就是 pa .
1. int b = 20;
2. *ppa = &b;//等价于pa =&b
- **ppa 先通过 *ppa 找到 pa ,然后对 pa 进⾏解引⽤操作: *pa ,那找到的是 a .
1. **p = 30;
2. //等价于*pa = 30;
3. //等价于a = 30;
七、指针的具体类型
(1)、空指针
1. void* p;
空指针,也称无具体类型的指针(或叫泛型指针),通常可以用作函数的参数,它可以接收任意类型的地址,但空指针不可以直接进行指针的加减整数和解引用的运算
(2)、指针数组
1. char (*p)[];
2.int (*p)[];
3.float (*p)[];
4.double (*p)[];
指针数组的每个元素都是⽤来存放地址(指针)的。(用来存放指针的数组)
(3)、数组指针
1. char (*p)[];
2.int (*p)[];
3.float (*p)[];
4.double (*p)[];
那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。
(4)、函数指针
1. int (*p)(int,int);
2.// int (*p) (int,int);
3.// ↑ ↑ ↑ ↑
4.//函数返回类型 函数指针变量名 p所指向的的函数的参数类型和个数说明
函数指针是用来变量是用来存放函数地址的,未来通过地址可以调用函数
(5)、函数指针数组
1.int (*p[4])(int,int)
2.// int (*p [4]) (int,int);
3.// ↑ ↑ ↑ ↑ ↑
4.//函数返回类型 函数指针数组变量名 存放函数的个数 p所指向的的函数数组中函数的参数类型和个数说明
总结 :
本文主要用于个人学习和知识分享,学习路漫漫,如有错误,感谢指正。
如需引用,注明地址。