文章目录
深入理解指针
指针学习是C语言中十分重要的一个环节,利用指针,学会了指针的知识后,我们会对系统的内存有更为清晰的认识,也对更为底层的程序,这样就是C语言经久不衰的原因。
地址
内存被划分为以字节为单位的空间
每一个字节都有一个编号,这个编号就是这块空间的地址。
地址要被存起来,需要一个空间,这个空间 就是指针变量
注意:
-
指针就是一块变量,用来存放地址,地址唯一标识一块内存空间
-
指针的大小是固定的4/8字节
-
指针是有类型的,指针的类型决定了指针的±步长,指针解引用操作的时候的权限
一个整形指针+1:跨过四个字节;一个字符型指针+1,跨国一个字节
-
指针的运算
接下来我们来了解各种指针:
字符指针
常见的字符指针有下面两种情况:
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
是否相等。
指针数组
指针数组是一个存放指针的数组
#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;
}
数组指针
数组指针的定义:
数组指针是数组还是指针?是指针
以此类推:
这里有两个指针,分别是什么类型?
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
就是数组首元素的地址,所以我们对数组指针解引用后得到的其实是数组首元素的地址
我们利用这一点来完成一个打印数组的程序
打印一维数组
#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)
:
结果:
打印二维数组
#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;
}
一维数组传参
一维数组传参有两种情况:
情况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;
}
二维数组传参
当二维数组作为一个函数的实参时,形参可以是怎样的?
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)//不可以
{}
一级指针传参
-
当一级指针为实参时:我们最好采用指针的形式去接收(而不是数组)
-
当一级指针为形参时,我们的实参可以是什么?
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;
}
举一个新的例子:
怎样使用函数指针
#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 (*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 y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("********************\n");
printf("*** 1.add 2.sub ***\n");
printf("*** 3.mul 4.div ***\n");
printf("*** 0.exit ***\n");
printf("********************\n");
}
int main()
{
int option = 0;
int x = 8;
int y = 4;
printf("请输入x,y:");
scanf("%d %d", &x, &y);
menu();
printf("请选择:>");
scanf("%d", &option);
switch (option)
{
case 0:
printf("退出");
break;
case 1:
printf("%d\n",Add(x, y));
break;
case 2:
printf("%d\n", Sub(x, y));
break;
case 3:
printf("%d\n", Mul(x, y));
break;
case 4:
printf("%d\n", Div(x, y));
break;
default:
printf("输入错误");
break;
}
return 0;
}
如果我们利用函数指针数组来实现这一个代码:
#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 y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("********************\n");
printf("*** 1.add 2.sub ***\n");
printf("*** 3.mul 4.div ***\n");
printf("*** 0.exit ***\n");
printf("********************\n");
}
int main()
{
int (*pfarr[4])(int, int) = { Add,Sub,Mul,Div };
int option = 0;
int x = 8;
int y = 4;
printf("请输入x,y:");
scanf("%d %d", &x, &y);
menu();
printf("请选择:>");
scanf("%d", &option);
if (option == 0)
{
printf("退出");
}
else if (option > 0 && option < 5)
{
printf("%d", pfarr[option](x, y));
}
else
{
printf("输入错误");
}
return 0;
}
我们利用一个函数指针数组将多个函数指针存在一个数组中,调用的时候更方便,只需要更改函数指针数组中的下标就可以实现对不同函数的调用(所以函数指针数组也被称为转义表)。
但是应该注意:在利用函数指针数组储存函数指针时,应该就确定所储存的每一个函数指针的类型完全相同(包括返回值和参数)。