没人比我懂C指针
文章目录前言指针概念前言C的地位不必多说,开山鼻祖。常年在各种排名中霸榜,地位不可撼动。红到发紫的Python也是C语言写的,Java也是C和C++写的,说白了,可以理解为Py是对C语言的更上一层的封装,说到这没有贬低Python的意思,只是说C语言真的很重要,如果你想了解Python的解释器,也可以叫做Python的虚拟机,你就必须会C,否则你就没有办法体会到Python的真谛,说白了,只能做一
1 前言
C的地位不必多说,开山鼻祖。常年在各种排名中霸榜,地位不可撼动。
红到发紫的Python也是C语言写的,Java也是C和C++写的,说白了,可以理解为Py是对C语言的更上一层的封装,说到这没有贬低Python的意思,只是说C语言真的很重要,如果你想了解Python的解释器,也可以叫做Python的虚拟机,你就必须会C,否则你就没有办法体会到Python的真谛,说白了,只能做一名掉包工程师罢了。
个人认为C语言之所有能够发展至今,经久不衰,跟指针是分不开的,还有我个人认为,C中最好的两个理念其一是指针,其二是typedef,我想用大白话跟大家讲一讲我对指针的理解和认识,希望能够帮助读者更好的理解指针。
2 指针概念
C语言是一种强类型语言,声明变量的时候要先定义这个变量的类型,按照不同的数据类型,C语言中分为整形变量,浮点型变量,布尔变量,结构体变量,枚举变量等等,在此不一一列举。
本文使用整形变量为例,说明C中怎么创建变量,并且为一个变量赋值,这个变量的值存在哪?这个变量的地址是多少?带着这些疑问,我先写一段代码,随后在画一张简图:
#include <stdio.h>
int main() {
int a = 0;
printf("a=%d\n", a);
printf("a的地址=%p\n", &a);
printf("a占用的存储空间=%d\n", sizeof(a));
int* ptr; // 表示指向地址存储的变量是整型
ptr = &a;
printf("ptr=%p\n", ptr);
printf("ptr指向的地址的值=%d\n", *ptr);
printf("ptr的地址=%p\n", &ptr);
return 0;
}
/*
a=0
a的地址=00AFF794
a占用的存储空间=4
ptr=00AFF794
ptr指向的地址的值=0
ptr的地址=00AFF788
*/
其实看到代码输出的结果和我画的示意图,应该对指针有一个大致的理解:
指针就是一个变量的地址,指针变量是一个存储变量地址的变量,变量就是可以改变的量,如上例子中,我可以定义一个int b = 1,ptr=&b,将ptr变量中的值改成B的地址,再去使用指针取值的时候*ptr就等于1,而不等于0。说了这么一大段,总结一句话,指针也是一个变量,只不过存储的是地址而已。
定义指针变量时一定要对其进行初始化,否则容易出现指针指向随机不明地址,导致意想不到的bug。如果没有一个合适的值给一个新建的指针,那么可以将指针初始化为NULL,NULL表示空指针。
3 指针使用场景
好好的变量不用,为啥非要搞指针呢?这东西有什么好处呢?先来看下面两个例子,好多说指针都使用两个数据交换做为案例讲解,虽然很简单但是我觉得数据交换对于小白来说还是比较难。
- 案例一:
使用函数修改一个变量的值,函数中使用普通整型变量,返回修改后的整型变量
#include <stdio.h>
int changeValue(int x, int value) { // 这个函数的作用是把x的值修改成value,最后在返回x
x = value;
return x;
}
int main() {
int a = 0;
printf("修改前a=%d\n", a);
a = changeValue(a, 10); // 使用a的值接收changevalue这个函数返回的整数值
printf("修改后a=%d\n", a);
return 0;
}
/*
修改前a=0
修改后a=10
*/
使用指针变量做为形参,修改一个变量值函数
#include <stdio.h>
void changeValue1(int* x, int value) {
*x = value;
return;
}
int main() {
int a = 0;
printf("修改前a=%d\n", a);
changeValue1(&a, 11);
printf("修改后a=%d\n", a);
return 0;
}
/*
修改前a=0
修改后a=11
*/
看过案例一之后,对指针有没有一点感觉呢,C中函数是通过传值来给形参赋值的,如果你想修改一个变量的值,那么必须使用一个返回值接收修改后的值,否则这个变量不会被改变;
如果你不想使用这个返回值接收修改后的值,那么请把这个变量的地址做为实参传递给被调用的函数,就比如实例一的第二个程序,这样代码就会简洁很多。
相信我,也相信自己,当你慢慢理解指针之后,你会爱上C!
下面来看看一个稍微复杂一点的例子!
- 案例二
修改一个指针变量的值,也就是说让指针变量指向一个其他的地址
int* changePtrVal(int* ptr, int* newPtr) {
ptr = newPtr;
return ptr;
}
int main() {
int a = 0;
int* ptr = &a;
int b = 1;
printf("修改前ptr=%p\n", ptr);
printf("修改前ptr指向地址存的值=%d\n", *ptr);
ptr = changePtrVal(ptr, &b);
printf("修改后ptr=%p\n", ptr);
printf("修改前ptr指向地址存的值=%d\n", *ptr);
return 0;
}
/*
修改前ptr=010FFD3C
修改前ptr指向地址存的值=0
修改后ptr=010FFD24
修改前ptr指向地址存的值=1
*/
由上面的程序可以看出来,通过changePtrVal函数将指针变量ptr的值修改成&b的地址。
使用双重指针来做参数,修改指针变量的值
void changePtrVal1(int **ptr, int* newptr) {
*ptr = newptr;
return;
}
int main() {
int a = 0;
int* ptr = &a;
int b = 1;
printf("修改前ptr=%p\n", ptr);
printf("修改前ptr指向地址存的值=%d\n", *ptr);
changePtrVal1(&ptr, &b);
printf("修改后ptr=%p\n", ptr);
printf("修改前ptr指向地址存的值=%d\n", *ptr);
return 0;
}
/*
修改前ptr=00F5FB1C
修改前ptr指向地址存的值=0
修改后ptr=00F5FB04
修改前ptr指向地址存的值=1
*/
通过上面的例子应该能够理解指针做为形参的意义和作用,当你想修改一个值,就请传入这个值的地址,也就是指针,如果你想用函数返回值的方式修改,那么就传入一个值。
传值就是把一个变量的值做为实参传给形参
传址就是把一个变量的地址做为实参传给形参
4 指针和数组
一维数组
只要谈到指针数组是不可避免的话题。
int main() {
int a[5] = { 0,1,2,3,4 };
printf("a = % p\n", a);
printf("& a[0] = % p\n", &a[0]);
printf("& a[1] = % p\n", &a[1]);
printf("& a[2] = % p\n", &a[2]);
printf("& a[3] = % p\n", &a[3]);
printf("& a[4] = % p\n", &a[4]);
return 0;
}
/*
a = 00D3F834
& a[0] = 00D3F834
& a[1] = 00D3F838
& a[2] = 00D3F83C
& a[3] = 00D3F840
& a[4] = 00D3F844
*/
数据名a就是指向数组首地址的指针
int main() {
int a[5] = { 0,1,2,3,4 };
printf("*a = % d\n", *a);
printf("*(a+1) = % d\n", *(a+1));
printf("*(a+2) = % d\n", *(a+2));
printf("*(a+3) = % d\n", *(a+3));
printf("*(a+4) = % d\n", *(a+4));
return 0;
}
/*
*a = 0
*(a+1) = 1
*(a+2) = 2
*(a+3) = 3
*(a+4) = 4
*/
通过上面这两个例子,我希望读者能够理解下面的公式:
a[i] = *(a+i)
数组名就是数组首元素的地址,a+1=a[1]的地址,指针变量可以进行加减运算,前提是要知道指针所指向的地址所占的字节数,看下面的例子,理解指针的加减运算。
int main() {
int a[5] = { 0,1,2,3,4 };
char c[5] = {'H','e','l','l','o'};
printf("a = %p\n", a);
printf("a+1 = %p\n", a+1);
printf("c = %p\n", c);
printf("c+1 = %p\n", c + 1);
return 0;
}
/*
a = 012FFD38
a+1 = 012FFD3C
c = 012FFD28
c+1 = 012FFD29
*/
指针的加减运算就是指针的跨度问题,不同类型的指针跨度不同,整型+1,地址会加四个字节,字符型+1,地址会加一个字节。
二维数组
int main() {
int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
printf("a = %p\n", a);
printf("a+1 = %p\n", a+1);
return 0;
}
/*
a = 00B3FD00
a+1 = 00B3FD10
*/
a~a+1 垮了16个字节,是因为四个int型总共占用16个字节
int main() {
int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
printf("*(*(a)) = %d\n", *(*(a)));
printf("*(*(a+1)) = %d\n",*(*(a+1)));
return 0;
}
/*
*(*(a)) = 1
*(*(a+1)) = 5
*/
通过上面的代码和图片,读者应该对下面的式子有一定的理解
a[0][0] = *(*(a+0))
在定义一维数组的时候,可以直接写int a[] = {1,2,3}…
在定义二维数组的时候,可以直接写int a[][3] = {{1,2,3},{4,5,6}}
但是当你写a[][]的时候编译器就会给你报出错误
这个可以这样理解,对于数组这种连续的数据存储结构,是需要使用指针进行数据读取的,数组名就是首地址,对于一维度数组来说,首地址+1,就是数组名[1]的地址,只要定义了数组类型,对于一维度数组来说就知道地址间隔。对于二维数组b[3][4]来说,b+1是b[1]的地址,如果不写a[3][4],就不知道指针+1是多少,这段可能比较抽象,我不知道自己的表达是不是清晰。
字符数组
字符数组是一种特殊的数组,C中没有字符串数据类型,如果想定义一个字符串,有两种方式:第一种是字符数组,第二种是字符指针。
字符数组
int main() {
char ch[10] = { 'H','e','l','l','o'};
printf("ch = %s\n", ch);
return 0;
}
字符指针
int main() {
char* str2;
str2 = "C Language";
printf("str2 = %s\n", str2);
return 0;
}
C语言存储字符数组时,会使用‘/0’做为一个字符串的结束符。
5 指针数组和数组指针
这两个概念很多人傻傻分不清
指针数组
指针数组是一个数组,数组的元素是指针。
*arrPtr[3] 下标运算符和解引用运算符具有相同的优先级,自右向左结合
int main() {
int a = 1, b = 2, c = 3;
int* arrPtr[3] = { &a,&b,&c };
for (int i = 0; i < 3; i++) {
printf("arrPtr[%d] = %p存储的值=%d\n", i, arrPtr[i],*arrPtr[i]);
}
return 0;
}
/*
arrPtr[0] = 012FFD1C存储的值=1
arrPtr[1] = 012FFD10存储的值=2
arrPtr[2] = 012FFD04存储的值=3
*/
这个例子其实没有实际意义,再举一个例子
int main() {
char ch1[] = "Hello";
char ch2[] = "World";
char ch3[] = "Fei";
char* chPtr[3] = { ch1,ch2,ch3 };
for (int i = 0; i < 3; i++) {
printf("chPtr[%d] = %p存储的值=%s\n", i, chPtr[i],chPtr[i]);
}
return 0;
}
/*
chPtr[0] = 012FFB38存储的值=Hello
chPtr[1] = 012FFB28存储的值=World
chPtr[2] = 012FFB1C存储的值=Fei
*/
数组指针
(*ptr)[3] ptr是一个指针,指向的是一个数组
int main() {
int a[3] = { 1,2,3 };
int(* ptr)[3] = &a;
for (int i = 0; i < 3; i++) {
printf("%d\n", (*ptr)[i]);
}
return 0;
}
/*
1
2
3
*/
本篇文章没有复杂的案例,旨在让小白或者对C指针迷惑的朋友能够清晰指针的含义。
更多推荐
所有评论(0)