深入理解C语言的指针
Posted 头疼的太阳花
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解C语言的指针相关的知识,希望对你有一定的参考价值。
文章目录
深入理解指针
指针学习是C语言中十分重要的一个环节,利用指针,学会了指针的知识后,我们会对系统的内存有更为清晰的认识,也对更为底层的程序,这样就是C语言经久不衰的原因。
地址
了解指针之前我们要了解地址
内存被划分为以字节为单位的空间
每一个字节都有一个编号,这个编号就是这块空间的地址。
地址要被存起来,需要一个空间,这个空间 就是指针变量
注意:
-
指针就是一块变量,用来存放地址,地址唯一标识一块内存空间
-
指针的大小是固定的4/8字节
-
指针是有类型的,指针的类型决定了指针的±步长,指针解引用操作的时候的权限
一个整形指针+1:跨过四个字节;一个字符型指针+1,跨国一个字节
-
指针的运算
硬件:
CPU 32位虚拟地址空间—>32bit的地址
64位虚拟地址空间—>64bit的地址
接下来我们来了解各种指针:
字符指针
常见的字符指针有下面两种情况:
int main()
//情况1:
char ch = 'a';
char* cp = &ch;
//情况2:
char* cp2 = "abcdef";
return 0;
情况1是我们常见的字符变量与指针指针,我们可以通过对指针的解引用来改变字符变量的值
情况2 比较特殊,字符串被储存在内存中的只读数据区(我们称为常量字符串),无法被更改
如果我们仍然去更改字符串的数据就会报错:
为了避免出现这种情况,我们可以在创建指向字符串的指针变量前面加上const
去限定该变量指向的内容不能再被修改:
const char *p = "hello";
指向常量字符串的指针与指向字符数组的指针的区别:
#include<stdio.h>
int main()
char str1[] = "abcdef";
char str2[] = "abcdef";
const char* str3 = "abcdef";
const char* str4 = "abcdef";
if (str1 == str2)
printf("str1==str2\\n");
else
printf("str1!=str2\\n");
if (str3 == str4)
printf("str3 == str4\\n");
else
printf("str3 != str4\\n");
return 0;
上面这个代码块中创建了两个字符数组,并且两个数组的内容是一样的,然后创建了两个字符指针指向了相同的常量字符串。
然后需要我们判断str1
与str2
是否相等、str3
与str4
是否相等。
分析:
str1 与 str2是两个数组,数组名代表首元素的地址,数组中存储的内容是相同的,但是这两个数组在内存中的位置是不同的,我们也可以随时更改数组的内容。
str3 与 str4是两个指向常量字符串的地址,常量字符串存储在内存中的只读数据区,常量字符串不能被更改,所以同一个常量字符串只会有一个地址(提高了内存利用率),因此str3 与str4 存储的地址是相同的。
指针数组
指针数组是一个存放指针的数组
#include<stdio.h>
int main()
char *arr[] = "abcdef","qwer","hello";
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i = 0; i <sz; i++)
printf("%s\\n",arr[i]);
return 0;
数组
arr
就是一个指针数组,数组的每一个元素都是一个指针,并且在这个arr
数组中的每一个指针元素所指向的都是常量字符串。在我们需要访问某一个字符串的时候,我们 只需要放为对应下标的数组。
数组指针
数组指针的定义:
数组指针是数组还是指针?是指针
字符指针-----指向指针的指针
char ch = 'q'; char *cp = &ch;
整形指针-----指向整形的指针
int num = 0; int *P = #
以此类推:
数组指针-----指向数组的指针
这里有两个指针,分别是什么类型?
int *p1[10];
int (*p2)[10];
“&数组名”和“数组名”
对于下面的数组:
int arr[10];
我们思考一下:
arr
与&arr
到底是什么?
在前面的学习中,我们知道数组名就是数组首元素的地址。
那么取地址数组名又是什么呢?
我们来看这样一段代码:分别打印了数组名的地址,数组首元素的地址,取地址数组名的地址
#include<stdio.h>
int main()
int arr[10] = 0 ;
printf("arr = %p\\n", arr);
printf("arr[0] = %p\\n", &arr[0]);
printf("&arr = %p\\n", &arr);
return 0;
打印出来的结果是相同的:
我们知道前面两个会打印出来相等的地址(前面我们说过数组名绝大多时候就是首元素的地址),但是我们看到在打印取地址数组名的时候也打印出来了相同的结果?是因为取地址数组名其实就等于数组名吗?结果是否定的。
我们利用下面这个代码段来看出取地址数组名和数组名的区别
#include<stdio.h>
int main()
int arr[10] = 0 ;
printf(" %p\\n", arr);
printf(" %p\\n", arr+1);
printf(" %p\\n", &arr[0]);
printf(" %p\\n", &arr[0]+1);
printf(" %p\\n", &arr);
printf(" %p\\n", &arr+1);
return 0;
思考:为什么会跨国40个字节呢?
我们知道这个数组是整形数组,包含十个元素,该数组的大小就是40个字节。所以&arr+1
相当于跳过了这一个数组,所以&arr其实是整个数组的地址,我们利用其他的数据类型来解释:
所以&数组名其实是整个数组的地址
那我们怎么存放数组的地址呢?使用数组指针
int (*p)[10] = &arr;
p的类型是:int(*)[10]:表示一个指向整形数组的指针,该数组有十个元素
根据上面的代码我们发现:
其实取地址数组名和数组名虽然说值是一样的,但是他们的意义是不同的
实际上:
&arr
表示的数组的地址,而arr
表示的是数组首元素的地址本例中的
&arr
的类型是int(*)[10]
,是一种数组指针类型数组的地址+1,会跳过整个数组的大小,所以
&arr+1
与&arr
的差值是40
数组指针的使用
使用数组指针时应该知道的是:解引用数组指针会得到什么?
同时我们知道arr
就是数组首元素的地址,所以我们对数组指针解引用后得到的其实是数组首元素的地址
我们利用这一点来完成一个打印数组的程序
打印一维数组
#include<stdio.h>
void print(int(*p)[10],int n)
int i = 0;
for (i = 0; i < n; i++)
printf("%d ", *(*p + i));
int main()
int arr[10] = 1,2,3,4,5,6,7,8,9,10 ;
print(&arr,10);
return 0;
解释为什么printf
函数的参数设置为*(*p+i)
:
前面我们知道:
*p
等于arr
---->*p
等于数组首元素的地址,(*p+i)
表示的数组的第几个元素的地址,再解引用就得到数组中第某个元素的值。
结果:
打印二维数组
#include<stdio.h>
void print(int(*p)[5],int r,int c)
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
for (j = 0; j < c; j++)
printf("%d ",*(*(p+i)+j));
printf("\\n");
int main()
int arr[3][5] = 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 ;
print(arr, 3, 5);
return 0;
解释为什么
printf
函数的参数设置为:*(*(p+i)+j)
:
函数传参的时候传递的参数是arr
,arr
是首行的地址,
首行是一个包含五个元素的数组,所以我们用int(*p)[5] = arr
;
此时p
等于首行的地址,(p + i
)是第i
行的地址
*(p+i)
表示的时候第i
行的首元素的地址)
(*(p+i)+j)
表示的是第i
行的第j
个元素的地址
*(*(p+i)+j))
表示的是第i
行第j
个元素的值
写代码的时候难免要把数组或者指针传给参数,那么此时函数的参数应该如何设计呢?
一维数组传参
一维数组传参有两种情况:
情况1:实参为普通一维数组—>有三种形参满足传参要求
//1、利用数组来作为形参
void test(int arr[10])
//2、仍然是一个数组,他和第一个情况的区别是没有指明数组的元素个数,但是这里也是满足条件的。
//原因:当我们利用一个数组作为形参的时候,不管有没有明确形参中数组的元素个数,
// 内存中都不会为了这个形参去创建一个数组。
void test(int arr[])
//3、利用指针来作为形参
void test(int *arr)
//其实三种方式本质上是相同的,都是将数组首元素的地址接收到,然后利用首元素的地址去访问其他元素的。
int main()
int arr[10];
test(arr);
return 0;
情况2: 实参为指针数组---->有两种形参满组传参要求
void test2(int*arr[20])
void test2(int **arr)
int main()
int *arr[20];
test2(arr);
return 0;
分析
情况1:直接用一个和实参相同的数组来作为形参,肯定是可以的
情况2:利用了一个二级指针来作为形参,二级指针存放的是指针的地址,恰好
arr
是一个指针数组(里面的每一个元素都是一个指针),所以可以使用二级指针来作为该函数的形参。
二维数组传参
当二维数组作为一个函数的实参时,形参可以是怎样的?
int main()
int arr[3][5] = 0;
test(arr);
return 0;
下面有几种形参的情况,判断哪些是可行的。
//1.
void test(int arr[3][5])//可以
//2.
void test(int arr[][])//不可以
//3.
void test(int arr[][5])//可以
注意:二维数组传参,形参如果为数组的话,那么二维数组的列不能省略
//4.
void test(int *arr)//不可以
//5.
void test(int*arr[5])//不可以
//6.
void test(int (*arr)[5])//可以
//7.
void test(int **arr)//不可以
解释:
4.形参是
int *arr
,该形参是一个指针,指向一个整形数据,但还是我们的实参是一个二维数组,类型不匹配,如果实参是一维数组就满足条件。5.该形参是一个指针数组,但是实参是二维数组,指针数组是存放指针的,两者类型不匹配。
6.该形参是一个数组指针,指向一个元素个数为5的数组,恰好实参是二维数组,并且第二维数组的个数为5,满足条件。
7.二级指针应该是用来储存一级指针的地址的,但是实参是一个二维数组,数组类型和指针类型不匹配。
一级指针传参
-
当一级指针为实参时:我们最好采用指针的形式去接收(而不是数组)
-
当一级指针为形参时,我们的实参可以是什么?
void test(int *p)
int main()
int a = 0;
int *pa = &a;
int arr[10];
test(&a);
test(pa);
test(arr);
return 0;
由上面的代码段可知:
当形参为一级指针时,我们有三种数据可以作为实参(前提是类型匹配):
- 变量的地址
- 一级指针
- 一维数组名
二级指针传参
-
二级指针为实参时,用二级指针接收就行
-
当二级指针为函数的形参时,函数的实参可以是什么?
void test(char **p)
int main()
char ch = 'w';
char*p = &ch;
char **pp = &p;
char *arr[5];
test(&p);
test(pp);
test(arr);
return 0;
函数指针
什么是函数指针
函数指针就是指向函数的指针
#include<stdio.h>
int add(int x,int y)
return x + y;
int main()
printf("%p\\n", &add);
printf("%p\\n", add);
return 0;
函数也有地址,函数的地址就是内存中储存这个函数的位置
注意:取地址函数名和函数名都代表了函数的地址,两者没有区别
如果我们想要把函数的地址储存起来,我们就需要函数指针。
下面是函数指针的创建方式:
#include<stdio.h>
int add(int x,int y)
return x + y;
int main()
int (*pf)(int,int) = &add;
return 0;
举一个新的例子:
如果存在这样一个函数:
void test(char*arr);
那么我们创建的函数指针就是这样的:
void (*pf)(char*)= &test;
怎样使用函数指针
#include<stdio.h>
int add(int x, int y)
return x + y;
int main()
int (*pf)(int, int) = &add;
int ret = (*pf)(3, 5);
printf("%d\\n", ret);
return 0;
思考:由于前面我们说的对于一个函数add
来说:&add
和add
是相等的,所以我们是否可以让函数指针这样来存储函数的地址?
int (*pf)(int,int) = add;
答案是肯定的,不仅如此,我们再用解引用指针来调用函数的时候还可以采取省略*
.
#include<stdio.h>
int add(int x, int y)
return x + y;
int main()
int (*pf)(int, int) = add;//存储地址的时候没有用&符号
int ret = pf(3, 5);//利用指针调用函数的时候没有用*符号
printf("%d\\n", ret);
return 0;
可以看到结果是相同的:
得到结论(四者的关系):
对于这样的函数以及函数指针:
int add(int x, int y); int (*pf)(int, int) = add;
存在:
&add
=add
=*pf
=pf
函数指针数组
函数指针是什么
存放函数指针的数组,每一个元素都是一个函数指针
假设存在这样一个函数指针是:
int (*pf)(int, int) = add;
那么如果我们需要用一个数组来存放该指针,我们的数组类型就应该和函数指针的类型是相同的:
int (*pf[10])(int,int);
这就是一个函数指针数组,它的类型是int(*)(int,int)
,它可以存放十个这种类型的指针。
函数指针的使用
函数指针有什么用处呢?
这里我们举一个计算机的例子:
#include<stdio.h>
int Add(int x, int y)
return x + y;
int Sub(int x, int y)
return x - y;
int Mul(int x, int以上是关于深入理解C语言的指针的主要内容,如果未能解决你的问题,请参考以下文章