万字长文搞定C语言指针
目录:
1.指针是什么?
2.定义和使用指针变量
定义指针变量
指针的初始化、赋值、取值
指针变量的交换
3.指针变量作为函数参数
4.通过指针引用数组
指针引用一维数组
指向一维数组的指针+1
指针变量作为函数的参数
指针指向多维数组
指向一维数组的指针
5.通过指针引用字符串
字符形指针变量
字符串指针变量和字符数组的比较
6.指向函数的指针
函数指针
返回指针值的函数
7.指针数组和多重指针
指针数组
指向指针变量的指针
8.动态内存分配与指向它的指针变量
动态内存分配与C语言内存模型
动态内存分配与释放函数
前言:指针是C语言最重要的一块知识,也是我们必须要掌握的内容,对于初学,可能很难,但是迎难而上才是我们学习必须有的态度。由于博主水平有限,如果博客中出现错误,还忘指正,博主会在第一时间修改
1.指针是什么?
在我们学习C语言的过程中难免会定义变量,如:
int n=1;对程序进行编译的时候会根据n的数据类型为n分配内存,我们通过前面的学习知道,int类型的数据在内存中占据4个字节,内存区的每一个字节都有一个编号,这个编号叫地址(这就像人的名字一样,一个名字对应一个人)。地址指向变量单元。实际上,计算机是通过变量名找到存储单元的地址,对变量值的存取都是通过地址进行的,比如上边定义的变量n
这里我们区分几个概念:
地址就相当于一个旅馆房间的门牌号,变量单元就相当于房间,存放的数据就相当于房间里边的人。
接下来我给出指针的概念:
一个变量的地址称为该变量的指针,如果有一个变量专门存储另一变量的地址,则称这个变量为指针变量
讲到这里不知道你有没有一个疑问,既然int型的变量有4个字节,每个字节有一个地址,那么这个变量的指针是四个字节中的哪一个字节的地址?
答案是第一个字节。口说无凭,我们做一个实验,我们知道一维数组的内存空间是连续分配的,而且数组中的每一个下标都相当于一个变量(如arr[[0],arr[1]…),那么我们打印数组中的连续两个下标就可以得出结论了
2.定义和使用指针变量
2.1定义指针变量
类型名 *指针变量名
int *p;类型名代表指针指向的数据的数据类型(也称基类型),比如上面定义的指针变量p 只能用来指向int类型的数据,不能指向浮点型数据。*指的是定义的变量是指针类型。
2.2指针的初始化、赋值、取值
指针可以定义时初始化如:
int a=1; int *p=&a;&a就是把a的地址传递给整形指针p
注意:注意不要把变量赋给指针,要把变量的地址赋给指针
同样的也可以定义后赋值如:
int a=1; int *p; p=&a;注意:再定义后初始化时p已经是一个指针类型的变量不要写成*p=&a;
当我们想要取得指针所指向对象的值时,我们需要*运算符
int a=1; int *p1=&a; printf("%d",*p); //以上程序打印结果:1 //如果你使用printf("%d",p);是以整形的形式打印p所代表内存中地址的编号2.3指针变量的交换
举个例子,请想一想下面的程序应该输出什么:
#include<stdio.h> int main() {int a=1;int b=2;int *p1=&a;int *p2=&b;printf("%d %d\n",*p1,*p2);int *p;p=p1;p1=p2;p2=p;printf("%d %d\n",*p1,*p2);printf("%d %d\n",a,b); }在p1和p2没交换之前在内存中的指向:
交换之后p1和p2在内存中的指向:
交换的只是指向,并没有交换变量单元里边的内容,如果把a和b的值交换了,就交换的是变量单元里边的内容,如下:
3.指针变量作为函数参数
看一个程序:
#include<stdio.h> void swap(int *a1,int *a2) {int temp=*a1;*a1=*a2;*a2=temp; } int main() {int a=1;int b=2;int *p1=&a;int *p2=&b;printf("*p1=%d *p2=%d\n",*p1,*p2);swap(p1,p2);printf("*p1=%d *p2=%d\n",*p1,*p2); }打印结果:
emm,没错就是这样的,是变量单元里内容的交换,好像自己又行了
再看一个程序:
嗯,是这样,地址交换,看一眼答案:
怎么会这样??????
地址交换,值应该变了啊,别急,首先我们要知道:
C语言里边的实参变量和形参变量的数据传递是值传递
什么意思呢?就是形参其实是实参的一个副本,这怎么讲呢,对于上面的那一段代码下面一张图:
p1把自己的值(表示地址)给了a1,p2把自己的值给了a2,也就是说就相当于新定义了两个指针变量和p1,p2指向一样。也就是说p1与p2指向始终未变,看下图,我们输出指针的地址:
如上所示,那么我们就可以知道,交换的只是a1和a2的指向,与p1和p2无关。如果你第一个程序不是这么分析,那么可以再试着分析一遍
4.通过指针引用数组
4.1指针引用一维数组
我们明确两个概念:
1.一维数组的内存分配是连续的
2.一维数组的数组名代表首元素的地址
所以我们可以得到如下:
int a[10]; int *p1=&a[0]; //int *p1=a;本行与上一行等价4.2指向一维数组的指针+1
我们知道,指针是一个字节的地址编号,那么当一个指针指向数组元素,指针+1是不是内存中下一个字节的地址,来看一个程序:
很明显,不是简单的地址+1,而是加上一个数组元素所占字节,其实不只是一维数组,任意类型的指针+1加上的都是指针基类型的字节数,我们看下面一个程序:
通过指针这个性质,我们可以可以使用指针找到指针任意位置的元素,所以可以用下面的方式遍历数组(注意:请不要随意访问你未申请的内存):
4.3指针变量作为函数的参数
我们知道当我们使用数组数组作为函数参数有:
void fun(int arr[],int len);其实程序在编译的时候就把arr[]当成指针变量处理,所以上面一行代码就等价于
void fun(int *arr,int len);那么以下两个函数(fun1与fun2)也是等价的:
这里有一个注意点:
实参数组名代表一个固定的地址,或者说是指针常量,但是形参数组名并不是一个固定的地址,而是可以看做一个指针变量
举个例子(程序编译运行会报错):
这是因为我们定义的一位数组首地址是一个指针常量,我们知道常量的值是无法修改的,所以我们使用arr=arr+3;这行代码就有问题。但是当我们把这个指针常量传给函数参数时,函数参数这个时候就是一个指针变量,这个时候就可以进行类似于arr=arr+3;的操作
4.4指针指向多维数组
int a[10][10];这里我们主要弄清几个概念:
a | 二维数组名,指向一维数组a[0],即0行首地址 | 行首地址 |
a[0], *(a+0), *a, &a[0][0] | 0行0列元素地址 | 元素地址 |
a+1, &a[1] | 1行首地址 | 行首地址 |
a[1], *(a+1) | 1行0列元素的地址,即a[1][0]的地址 | 元素地址 |
*(a[1]+2), *(*(a+1)+2), a[1][2] | 1行2列元素的值,即a[1][2]的值 |
备注的行首地址与元素地址什么意思呢?我们看一个程序:
我们知道a是代表行首地址,*a代表元素地址,使用整形的形式打印出他们的地址发现是一样的,那么你可能会问他们有什么区别,再看一个程序:
二维数组可以看做是多个一维数组为元素组成的数组,a映射到第一行(也就是第一个一维数组的首地址),*a则是映射到第一行第一列(也就是第一行第一个元素)的首地址:
第一行的首地址和第一个元素的首地址当然是一样的,因为地址是元素第一个字节的编号,只是两者映射的范围不同,映射的范围不同+1所产生的结果也不同(第二张图),比如上面两张图我们自己也可以算算,比如a是第一行数组的首地址6683776,那么a+1代表第二行数组的首地址,他俩中间隔着是10个整形元素,一个整形元素是4个字节,那么一共隔了4*10=40个字节,所以根据数组中的元素在内存中是连续分配的a+1的地址是a的地址+40,结合上面两张图a+1的地址是6683816印证了我们的想法。*a映射到的是元素,所以 * a+1映射到的就是下一个元素,一个整形元素四个字节,所以按照正常逻辑 他俩相差四个字节,上面的两张图,*a地址是6683776,*a+1的地址是6683780,这就印证了上面的讲解。补充一点:a和a[0]都是指向第一行,只是不同的表现形式而已
4.5指向整个一维数组的指针
int (*p)[4];以上定义p表示为一个指针变量,它指向包含四个整形元素的一维数组,注意它指向的是整个一维数组,而不是一个整形元素
int a[4];p和a是不同的,a是映射到数组第一个元素,p是映射到整个一维数组,看下面程序应该就知道我在说什么了:
用指针数组的指针作为函数的参数时:
#include<stdio.h> void func(int (*p)[8]) {} int main() {int arr[10][8]={0};func(arr); }这个程序注意两个细节:
1.由于p映射整个一维数组,所以传参的时候传递的是二维数组名
2.由于函数参数中p是指向含8个整形元素的一维数组,所以传入的二维数组的每一个一维数组长度也是8
5.通过指针引用字符串
5.1字符形指针变量
char *str="www.baidu.com";C语言通过字符串指针变量(str)来引用字符串常量,同时字符串常量(www.baidu.com)按照字符数组处理;str映射到的是第一个字符的地址(str+1就映射到第二个字符的地址)
注意:通过字符数组或字符指针变量可以输出一整个字符串,而对于一个数值型数组(int a[10]),是不能企图利用数组名输出它全部的数据的。
5.2字符串指针变量和字符数组的比较
char str1[20]="www.bilibili.com"; //字符数组只能定义的时候赋值,不能定义后使用str1[20]="www.bilibili.com"; char *str2="www.bilibili.com"; //对于指针变量来说可以定义后再赋值如:str2="123123";注意:
1.字符数组里边的每一个元素存放字符串的一个字符,字符串指针变量存放的是字符串首个字符的地址
2.str1是指针常量,str2指针变量
3.编译时为字符数组分配若干个存储单元,而对字符串指针变量只分配一个存储单元
4.字符数组中的各个元素是可以改变的,字符串指针变量指向的字符串常量是不可被改变的,但是字符串指针变量的指向是可以改变的
6.函数与指针
6.1函数指针
如果我们在程序中定义了一个函数,那么编译系统会为函数分配一段存储空间,这段存储空间的其实地址称为函数的指针
#include<stdio.h> int max(int a,int b) {if(a>b)return a;return b; } int main() {int (*p)(int,int);//p只能指向返回值为int类型,参数也是两个int类型的函数p=max;int a=1;int b=2;printf("%d",p(a,b)); }同一个函数指针可以先后指向同类型的不同函数
6.2返回指针值的函数
顾名思义,返回指针类型其实就是返回地址类型得函数,一般得定义形式为:
类型名 *函数名(参数列表)例子:
#include<stdio.h> int *max(int *a,int *b) {if(*a>*b)return a;return b; } int main() {int a=1;int b=2;int *p=max(&a,&b);printf("%d",*p); }7.指针数组和多重指针
7.1指针数组
定义格式:
类型名 * 数组名[数组长度]例子:
再看一个程序:
str[0]存放字符串”aaaio"中第一个字符的地址,str[1]存放字符串"bbb"中第一个字符的地址。对于一个指针数组来说,注意,这里和二维数组不同的是:str不是指向第一行的地址,str里边存放的是str[0]的地址,相当于一个二级指针,如下(str与str[0]的地址):
str,str[0]与str[0][0]的关系就相当于,str是门牌号1,打开门牌号1对应的门,里边有一个门牌号2,这个门牌号2就是str[0],再打开门牌号2的门就可以找到元素str[0][0]
一定要注意指针数组存放的是内容是地址
这里还有一个知识点,你可以发现指针str,str+1,str+2(也就是二级指针)相差八个字节,那么也就是一个二级的char类型的指针占据八个字节,如下:
那么是不是不同类型的指针所占的内存空间大小不同,做一个实验(sizeof函数返回所占字节数):
结论:
在64位计算机中,不管什么类型的指针,都占据8个字节
注意:这里只是64位计算机,32位计算机指针占据四个字节
7.2指向指针变量的指针
当一个指针指向一个普通数据的时候,把这个指针称为一级指针,指针变量既然是变量,那么肯定也是占据内存中的,所以我们还可以用指针指向这个一级指针的的存储空间,这时候指向一级指针的指针就叫做二级指针,同理,指向二级指针的指针称为三级指针。
举个例子:
a[0]相当于存放数据元素1的地址,a[1]相当于数据元素2的地址,a存放a[0]这个整形指针的地址,a+1相当于存放a[1]这个整形指针的地址。a与p就相当于两个二级指针。
8.动态内存分配与指向它的指针变量
8.1动态内存分配与C语言内存模型
我们复习几个概念:
1.局部变量是按照动态存储方式分配内存(分配在动态存储区)
2.全局变量是按照静态存储方式分配内存(分配在静态存储区)
动态存储区分为堆和栈,动态内存分配就是分配堆中的内存空间,因为堆中的内存空间是程序员自行分配和释放的
C语言的内存模型分为5个区:栈区、堆区、静态区、常量区、代码区。每个区存储的内容如下:
栈区 | 存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈 |
堆区 | 就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放。分配方式类似于数据结构中的链表。“内存泄漏”通常说的就是堆区。 |
静态区 | 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后,由系统释放。 |
常量区 | 常量存储在这里,不允许修改。 |
代码区 | 顾名思义,存放代码 |
8.2动态内存分配与释放函数
1.void *malloc(unsigned int size):作用是在动态存储区中分配一个长度为size的连续空间,unsigned代表没有符号位的整形数据(非负整数),返回所分配内存区域第一个字节的地址.分配失败返回NULL指针
2.void *calloc(unsigned n,unsigned size):作用是在动态内存空间中分配n个长度为size的连续空间,分配失败返回NULL指针
3.void free(void *p):释放指针变量p所指向的动态空间
4.void *realloc(void *p,unsigned int size):对已经通过malloc函数calloc函数获得了动态空间,想改变其大小,用此函数重新分配
注意:void*类型的指针表示指向空类型或者不指向确定的类型的数据
以上函数得使用#include<stdlib.h>
使用举例:
#include<stdio.h> #include<stdlib.h> int main() {int i=0;int *p=(int*)malloc(4);//(函数前的int*代表把分配的内存转换成int*类型)*p=3;printf("%d\n",*p);free(p); }总结
以上是生活随笔为你收集整理的万字长文搞定C语言指针的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 关于printf()与自增自减运算符结和
- 下一篇: 为啥地址线是20根则存储单元个数为2的2