文章目录
C语言指针
最近在看一本关于【C语言】的书,普通语法没什么,就是指针这个地方确实有点难,现在写一点关于我对指针的理解,当然肯定会有理解不到位的地方,希望多多担待。
1. 默认你有其他语言基础比如Java,Python,C++等语言基础 2. 默认你对计算机有所了解 3. 默认你想学习,而不是当个热闹看
那么到底什么才是指针呢?
指针就是一串代表内存地址的数字,通常使用十六进制来表示。
正因为我们可以直接对内存地址进行操作,所以我们才说C语言功能强大。
说起指针,以我目前所学,我认为暂时可以分为以下几种类型:
- 普通指针——指向一个变量的指针
- 数组指针——能对数组进行操作的指针
- 函数指针——指向函数的指针
pass:为什么数组指针不说是指向数组的指针。
这个原因会在数组指针的地方,对数组进行分析,让你了解数组的形成。这里就不多做赘述了
1. 普通指针
什么是普通指针,普通指针就是指向基本数据类型的指针,比如int 、float等。
1.1 指针的定义
我认为实战是最好的理解方式,所以会有代码以及注释详细理解,不过在你看代码之前,你应该知道这些东西:
- 如何定义一个指针
- 如何给指针赋值
- 给指针赋值后,怎么使用原变量的值
就和定义一个普通变量一样:类型 *变量名
指针变量接收的是变量的内存地址
在C语言中,通过符号**&**来取出变量的内存地址
赋值也是同样的
1.2 指针实例理解
那么你知道了这些知识后,就看代码:
#include<stdio.h> int main() { int num = 10; // 创建一个int类型的变量,并赋值为10 int* pnum; // 创建一个int类型的指针,你还能这样写 int *pnum pnum = # // &num是num在内存空间的内存地址 // 这句代码是将num的内存地址赋值给pnum printf("num的值为:%d\n&num的值为:%p\n*pnum的值为:%d\npnum的值为:%p",num,&num,*pnum,pnum); // 将各个值都打印出来看看效果 return 0; }
运行结果是:
num的值为:10 &num的值为:0xff8effe0 *pnum的值为:10 pnum的值为:0xff8effe0
1.3 头脑风暴
看了这段代码,是不是对指针有了更深刻的了解了呢?
如果你想学好,就暂停你的进度,思考一下:
- pnum是什么,他开辟的内存空间是多大
- *pnum是什么,有什么用
- &pnum是什么,他的作用是什么
思考之后来看看吧~~
那么我们看着代码和运行结果可以总结出以下内容:
接下来,你就想想你,你的身份证号,你的身份证,国家信息系统
- num是一个变量,这个变量可以对10进行操作
- 将10当成你,num是你的名字
- 声明一个变量后,内存空间会为变量开辟一个内存空间以及内存地址
- 而你出生后也会有一个身份证号
- &num是变量的内存地址,这里&num虽然是内存地址,但是不说&num是num的指针,因为指针是一个变量,俗称指针变量
- num在内存的内存地址相当于你的身份证号
- 你的身份证号只是一串数组,抽象存在
- pnun是一个指针变量,他的值是是&num,也就是一个普通变量的内存地址
- 把它当成你的身份证
- 你的身份账号在上面,就可以通过身份证号(指针)进行操作(买票,办卡等)
- *pnum是通过内存地址获取到该内存地址存储的值
- 就是通过你的身份证在国家系统找到了你
- 也可以对你进行操作(比如征信,车票等)
1.4 头脑风暴(二)
试想,你如果声明一个变量,并将该指针变量的内存地址给该指针,也就是让指针变量存储的是指针的内存空间,会有什么事情发生?
思考:我们的指针变量是一个存储内存地址的指针,但他同样也还是一个变量,所以也会在内存中有自己的内存地址,而刚好指针存储的就是变量!!等等等等,一拍即合,我们就把指针的内存赋给指针,看看会发生什么!
上代码:
#include <stdio.h> int main(int argc, char const *argv[]) { int * p; p = &p; printf("p=%p\n&p=%p\n*p=%p",p,&p,*p); return 0; }
编译结果:
p=0xfff75af4 &p=0xfff75afc *p=0x0
哦豁,结果清晰可见,我们也因此产生了一些疑惑,为什么会出现两个内存地址呢?
找到了原因!
昨天是使用手机敲得C代码,因为那时候还在火车上,没法拿电脑
今天使用了电脑,编译器是gcc,编辑器是vs code
重新编译了一下
运行结果如下:
p=0061FECC &p=0061FECC *p=0061FECC
欸,这就很舒服了,内存地址是一样的,所以虽然安卓有C语言的编译器,但还是使用电脑吧。
1.5 二级指针
今天使用了电脑,但是我们的代码却是不妥的,因为这里涉及到了一个二级指针
我们也收到了一条警告
assignment to 'int *' from incompatible pointer type 'int **' 翻译:从不兼容的指针类型'int **'赋值给'int *'
参考大佬的话,p是一级指针,&p是二级指针,那么问题来了,什么是一级指针,什么是二级指针
一级指针就是普通变量的指针
二级指针就是指针变量的指针,也就是指针的指针
就像这样:
#include <stdio.h> int main(int argc, char const *argv[]) { int test = 10; // 普通变量 int * p = &test; // 指向普通变量的指针,也就是一级指针 int ** pp = &p; // 指向指针的指针,也就是二级变量 printf("test的内存地址是%p\np的内存地址是%p\npp的内存地址是%p", &test, &p, &pp); return 0; }
运行结果:
test的内存地址是0061FECC p的内存地址是0061FEC8 pp的内存地址是0061FEC4
你就可以这么理解:几级指针,就嵌套了几个地址
1.7 指针常量
他和常量指针长得很像,但是他俩却差了很多,首先,指针常量是指针常量,而常量指针是常量指针。
与指针变量相区别,就和常量与变量的区别一样。常量是不可改变的,指针常量也是不可改变的,但是指针常量指向的普通变量却不是不可改变的。
也就是说,指针常量是一个常量,而我们在定义普通常量时通常是使用的const或者#define,而定义指针常量是使用
#include <stdio.h> int main(int argc, char const *argv[]) { /* code */ int num = 10; // 普通变量 int *const p = # // 指针常量 // 错误使用 int num2 = 11; p = &num2; // 因为是常量,无法再进行赋值 return 0; }
这个指针是常量,无法再进行赋值运算。
1.8 常量指针
这是一个指针,只不过他指向的是一个常量。
我们无法通过指针操作常量,但是可以对指针重新赋值。
#include <stdio.h> int main(int argc, char const *argv[]) { const int num = 10; // 常量 const int *p = # // 常量指针 // 错误使用 *p = 20; // 正确使用 int num2 = 20; p = &num2; return 0; }
2. 数组指针/指针数组
数组指针是:
指向数组的指针,它本质上还是一个指针,类比普通指针
指针数组是:
一个存放指针的数组,本质上是数组,就如经常说的字符数组,整型数组一样
2.1 数组的理解
数组本质上只是编译器在内存空间上开辟的一连串的内存
而代表数组的变量其实只是这一连串内存空间的第一个元素的内存地址。
所以当你给编译器看一个数组时,他并不是像人一样能看到这个数组的全貌,他只能看到这个数组的第一个元素,并且知道这个元素的内存地址
看看这串代码:
#include <stdio.h> int main(int argc, char const *argv[]) { int a[] = {1, 2, 3, 4}; // 一个数组 printf("a的内存地址%p\na[0]的内存地址%p", &a, &a[0]); return 0; }
他的运行结果为:
a的内存地址0061FEC0 a[0]的内存地址0061FEC0
相信你对数组有了更深的了解。
2.2 数组强制类型和下标
那么为什么定义数组需要强制类型呢?
拿int类型来说,int类型占用4个字节
在人们眼中的元素位置的+1
相当于编译器眼里的+4(4是类型占用的字节数)
所以才能精准的拿到某个元素
数组下标是怎么定义的呢?为什么下标从0开始
数组的下标也是这么来的,通过对内存地址的相加减来获取
因为编译器只记得数组第一个元素的内存地址
而下标就是让第一个元素的内存+i(i是下标)
通过下标获取元素的过程可以类比为:
- arr[1] => *(&arr +1)
先让内存地址加下标,再通过指针获取到元素
2.3 数组指针
数组指针就是指向数组第一个元素的指针,相信认真看了2.1和2.2的你能够很快理解
定义一个数组指针
int a[] = {1, 3, 5, 7}; // 一个数组 int (*p)[4] = &a; // 定义一个指针,指向数组的头元素
通过指针访问第二个数组元素:
printf("访问数组的第二个元素:%d", *(*p+1));
完整代码:
#include <stdio.h> int main(int argc, char const *argv[]) { int a[] = {1, 3, 5, 7}; // 一个数组 int (*p)[4] = &a; // 定义一个指针,指向数组的头元素 printf("a的内存地址%p\na[0]的内存地址%p\n", &a, &a[0]); printf("访问数组的第二个元素:%d", *(*p+1)); return 0; }
运行结果:
a的内存地址0061FEBC a[0]的内存地址0061FEBC 访问数组的第二个元素:3
2.4 指针数组
指针数组,顾名思义,他是个数组,就如经常说的字符数组,整型数组一样,只不过指针数组的定义方法和存储对象也有亿点点不一样。
定义一个指针数组(以整型为例)
int *pArr[10]; // 定义一个指针数组
要注意与数组指针的定义区别开
数组指针的定义:
int (*arrP)[10];
一定要注意这个括号,这涉及到了*符号的运算优先级,一但写错,就是不同的两个东西了。
简单使用:
#include <stdio.h> int main(int argc, char const *argv[]) { int *arr[10]; // 定义一个指针数组 int arrSize = 10; // 指针数组的长度 for (int i = 0; i < arrSize; i++) { arr[i] = &i; // 将临时地址放在指针数组里 printf("数组的元素:%p\n数组元素所指向的元素%d\n", *arr[i]); /* code */ } /* code */ return 0; }
输出结果:
数组的元素:0061FEA0 数组元素所指向的元素0 数组的元素:0061FEA0 数组元素所指向的元素1 数组的元素:0061FEA0 数组元素所指向的元素2 数组的元素:0061FEA0 数组元素所指向的元素3 数组的元素:0061FEA0 数组元素所指向的元素4 数组的元素:0061FEA0 数组元素所指向的元素5 数组的元素:0061FEA0 数组元素所指向的元素6 数组的元素:0061FEA0 数组元素所指向的元素7 数组的元素:0061FEA0 数组元素所指向的元素8 数组的元素:0061FEA0 数组元素所指向的元素9
因为i是临时变量,所以在每次循环之后都会销毁,下次使用再次开辟,所以内存地址是一样的。
3. 函数指针
在我们定义函数的时候,编译器也会在内存空间给函数开辟一个内存,而该内存的首地址就是函数的内存地址,而函数指针就是指向该内存地址的。
3.1 函数
众所周知,C语言是面向过程的语言,或者称函数式编程。
而在C语言中,函数也确实起了很大的作用,在C语言的学习中,你见过最多的可能就是main
函数,同时也是你第一个见得函数。
我们来看看这个main函数
int main(){return 0;}
我们把他浓缩成一行,比较好瞅
- int是返回类型,每个函数都要有这个,不返回东西的函数的返回值类型为void
- main是函数名,固定的,无法重载
- 括号里面是参数列表,一般是默认没有,也可以传递
void
或者int argc, char const *argv[]
- {}大括号里面是函数的具体实现代码,比如说
printf("Hello World!");
- return 是函数结束的关键字,返回值为0表示程序正确运行,为其他表示有其他异常
切记main函数不要
void main(){},这个真的很重要
3.2 指向函数的指针
见名知意,这个东西也是一个指针,只不过他指向的是一个函数,准确来说是函数在内存空间中开辟空间的头地址。
定义也是有亿点点麻烦,不过却也不是不好理解。
定义:
int (*funP)(int num1, int num2); // 定义一个函数,有两个整型参数
因为运算符优先级的存在,所以我们需要对变量名与*进行首先运算
使用:
#include <stdio.h> /* 定义一个两数求和函数 返回两个数的和的结果 */ int sum(int num1, int num2) { int ans = num1 + num2; return ans; } int main(int argc, char const *argv[]) { int (*funP)(int num1, int num2); // 定义一个函数,有两个整型参数 funP = sum; // 将函数sum的地址给funP int ans = funP(1, 2); // 使用指针使用函数 printf("%d", ans); return 0; }