目录
初阶指针的知识
1、指针就是个变量、用来存放地址,地址唯一标识一块内存空间。
2、指针的大小是固定的4/8个字节(32位平台/64为平台)。
3、指针的类型决定了指针的+-(加、减)整数的步长、指针解引用操作的时候的权限。
4、指针的运算
指针进阶的知识点
1、字符指针
字符指针char*
#include<stdio.h>
int main()
{
char ch = 'w';
char* pc = &ch
char* ps = "abcdef";//字符串常量不可以修改
//这里是把字符串abcdef的起始地址存入指针ps中。一个字符串指针中可以存入一个字符串的起始地址
char arr = "abcdef";
char* p = arr;//通过这两步来改变字符串中的字符
printf("%c\n",*pc);
printf("%c\n", ps);
return 0;
}
结果是:
w
abcdef
注释:1、当不需要对字符串改变时,可以用指针定义。若想修改,需将字符串放入一个可 修改的空间中,通过指针的操控来改变字符串。
2、如果一个指针指向了一个字符串,那我们就说p是一个指向字符串的指针。
3、字符串中的所有字符在内存中是连续排列的,p指向的是字符串的第0个字符; 通常我们将第0个字符的地址称为字符串的首地址。
例题:
#include<stdio.h> int main() { char str1[] = "hello bit.";//有独立空间 char str2[] = "hello bit.";//有独立空间 const char* str3 = "hello bit.";//常量字符串,不可修改 const char* str4 = "hello bit.";//从代码优化的角度来看指针str3和str4指向同一个地址 if(str1 == str2)//这里比的是地址,而不是内容,数组str1和str2表示的是数组首地址 printf("str1 and str2 are same\n"); else printf("str1 and str2 are not same\n"); if(str3 == str4)//这里比的也是地址 printf("str3 and str4 are same\n"); else printf("str3 and str4 are not same\n"); return 0; }
输出结果如图:
如果想要比较字符串的内容使用:strcmp
2、指针数组(本质就是数组)
指针数组顾名思义————就是存放指针的数组
int main()
{
char* arr[5] = {"abcdef","zhangsan","wangcai","ruhua"};//存放的是这五个字符串的起始
//地址(首字符的地址),在这里用来初始化
int i = 0;
for(i=0;i<5;i++)
{
printf("%s\n",arr[i]);
}
return 0;
}
int* arr1[10];//存放整型指针的数组
char *arr2[4];//存放一级字符指针的数组
char **arr3[5];//二级字符指针的数组
相关指针数组的内容在初阶指针中
3、数组指针
3.1数组指针的定义
数组指针————是指针,并指向数组
通过比对探讨数组指针
//整型指针 — 指向整型的指针 —存放整型变量的地址 int* p1; //字符指针 — 指向字符的指针 —存放字符变量的地址 char* p2; //数组指针 — 指向数组的指针 —存放一个大小为10个整型的数组的地址 int(*p3)[10];
int main()
{
int a = 10;
int *p1 = &a;
char ch = 'w';
char* p2 = &ch;
int arr[10] = {1,2,3,4,5};
int (* pa)[10] = &arr;//取出的是数组的地址存放到pa中,pa是数组指针变量
return 0;
}
注释:上述代码中int(*)[10]是数组指针的类型
3.2 数组指针的使用
数组指针在二维数组中用的比较多,在一维数组中使用较少
3.2.1 数组指针在一维数组中的使用
void print1(int(*p)[10],int sz)//指针变量p中存放数组arr的地址,指针变量p的类型是int(*)[10]
{
int i = 0;
for(i = 0 ; i < sz ; i++)
{
printf("%d ",(*p)[i]);//对p解引用得到arr,表示数组首元素地址,arr[i]表示下标位i数
//组元素
printf("%d ",(*arr+i));//在这里(*p)[i]与(*arr+i)表示的结果相同,&arr传给P的地址
//是数组首地址,arr表示的是元素首地址,解引用来表示数组元素
}
printf("\n");
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz =sizeof(arr)/sizeof(arr[10]);
print1(&arr,sz);//传给print1函数数组arr的地址
return 0;
}
3.2.2 数组指针在二维数组中的使用
void print(int (*p)[5],int r,int c)//二维数组arr将首地址传给p的时候,p指向二维数组第一 //行,二维数组的首地址就是第一行地址。r表示行,c表示列 { int i = 0; for(i = 0;i < r;i++) { int j = 0; for(j = 0;j < c;j++) { printf("%d ",*(*(p+i)+j));//*(p+i)表示的是第i行元素,相当于p[i],p+i指向第一 //行,p+i表示第一行地址. } printf("\n"); } } void test() { int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}}; print(arr,3,5);//传递二维数组首元素地址,二维数组首元素地址表示第一行5个元素 } int main() { test(); }
注释:上述代码中int(*p)[5]指向的是二维数组的第一行,上述场景下才是数组指针的常用形式
画图解释上述代码:
学习了指针数组和数组指针后,下面的代码意思就可以理解了
int arr[5]; 整型数组,存放5个整型
int *parr1[10]; 指针数组,存放10个int*类型指针的数组
int (*parr2)[10]; 数组指针,parr2指针指向一个存放10个int类型的数组
int(*parr[10])[5]; 为一个数组包含10个元素,每个元素为数组指针,指针指向一个存放5个int类
型的数组,简单来说就是一个存放数组指针的数组。
3.3 &数组名vs数组名
arr和&arr分别是啥?
arr是数组名,数组名表示数组首元素的地址。
&arr表示的是啥:
在这里从数值的角度看结果一样,但是他们代表的涵义还是有所不同
数组名是数组是首元素的地址有两个例外:
1、sizeof(数组名) //这里的数组名表示的是整个数组
2、&数组名 //这里表示的是数组,取出的是整个数组的地址
4、数组参数、指针参数
在写代码的时后难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
4.1、一维数组传参
#include<stdio.h>
void test(int arr[])//这里的数组大小可以省略,数组传参的时候,不会将整个数组传过去,那么接
//收的时候不需要创建一个数组来接收传过来的值,不需要创建数组,就不需要大小,所以数组的大小
//就省略了。数组传参的时候,传递的是数组首地址。
{}
void test(int arr[10])//前两个传参,形参是数组
{}
void test(int *arr) //数组传参,形参是指针的形式
{}
void test2(int* arr[20])//这里20也可以省略
{}
void test2(int **arr)//int* arr2[20]表示数组arr2中存放了20个int*类型的指针,形参
//int**arr中的两个**中的一个*表示arr是指针变量,指针arr指向数组arr2的首元素的地址,int*
//表示数组中存的是int*类型的指针
{}
int main()
{
int arr[10] = {0};
int* arr2[20] = {0};//这里是存放整型指针的数组
test(arr);
test(arr2);
}
数组传参,形参可以用数组接收,也可以用指针接收,以上的一维数组传参都是可行的。
4.2、二维数组传参
void test(int arr[3][5])//ok
{}
void test(int arr[][])//err
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字,因为对一个二维数组,可以不知道 //多少行,但是必须知道一行有多少元素,这样才方便运算
void test(int *arr)//err,
//这里传过来的是二维数组首元素的地址指(第一行地址),数组有多少列,无法知道
{}
void test(int* arr[5])//err
{}
void test(int (*arr)[5])//ok
{}
void test(int **arr)//err
//二维数组传参传过去的是第一行的地址,是一个一维数组的地址,二级指针用来接收一级指针变量的 //地址
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
注释:二级指针不能用来接收二维数组的传参,二级指针用来接收一级指针变量的地址
4.3、一级指针传参
#include<stdio.h>
void print(int *p,int sz)//形参用一级指针接收,接收的是指针p中的内容
{
int i = 0;
for(i = 0;i < sz;i++)
{
printf("%d\n",*(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
print(p,sz);//一级指针传参
}
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
void test(int *p)
{}
//1、整型变量的地址
int a = 0;
test(&a);
//2、整型数组的首元素地址
int arr[10] = {0};
test(arr);
//3、一级指针变量本身
int a = 0;
int *p = &a;
test(p);
4.4、二级指针传参
#include<stdio.h>
void test(int** ptr)//这里ptr和pp的内容相同,用法也相同。
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);//二级指针传参,用二级指针接收
test(&p);//传一级指针地址,用二级指针接收
return 0;
}
当函数的参数为二级指针的时候,可以接收什么参数?
void test(char** p)
{}
int main()
{
char c = 'b';
//1、一级指针变量的地址
char* p = &c;
//2、二级指针变量本身
char**pp = &p;
//3、指针数组的数组名
char* arr[10];
test(arr);
}
5、函数指针
函数指针表示指向函数的指针
先看一段代码
int Add(int x,int y) { return x + y; } int main() { printf("%p\n",Add);//函数取地址和不取地址结果都是相同的,拿到的都是函数的地址,函数和数组不同,数组存在取首元 //素地址,而函数不存在这样的问题 printf("%p\n",&Add); }
输出结果如图:
注释:上述代码表示Add和&Add都能让代码得到函数的地址,所以函数名Add和&Add都能代表函数地址。
函数指针的几种方式
int Add(int x,int y) { return x+y; } int main() { int(*pf)(int x,int y) = Add;//函数指针,将Add的地址传给指针pf int sum = (*pf)(3,5);//这样写更容易让人理解 //在函数调用中直接调用Add也可以 int sum = Add(3,5); int sum = pf(3,5);//这样写也可以 printf("%d\n",sum); }
注释:由第一段代码可知函数名可以表示函数地址,Add和pf一样都代表地址,所以在写代码是它俩可以用相同的语法来写。
学完函数指针,来解读两个有意思的代码:1、( *( void ( * ) ( ) ) 0 )( )
解读这个代码,从0为突破口,
表示把0直接转换成一个void (*) ( )的函数指针,然后去调用0地址处的函数
这个代码只是放在这里用作理解函数指针,
补充:
关于强制类型转换的知识点:
一般形式为:(类型说明符)(表达式)其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。
2、void( * signal( int , void( * )( int ) ) )( int );
上述代码是一次函数声明
声明函数叫:signal
signal函数的第一个参数是int类型对的
signal函数的第二个参数是一个函数指针类型,该函数指针指向的函数参数是int,
返回类型是void
signal函数的返回类型也是一个函数指针类型
6、函数指针数组
6.1函数指针数组的定义
数组是一个存放相同类型数据的存储空间,我们已经学了指针数组,
比如:
int* arr[10];
那要把函数的地址存放到一个数组中,那这个数组就叫函数指针数组,那函数指针数组如何定义呢?
int(*parr[10])();
parr先和[ ]结合,说明parr是数组,数组的内容为int(*)( )类型的函数指针
6.2、函数指针数组的使用
举例:写一个计算器进行整数的加、减、乘、除
#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 input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
printf("请输入两个操作数:>");//将这句代码写在这里,当input输入的是0或0~4以外的数,都要进行这两句代码,显然是存在问题的,
scanf("%d %d", &x,&y);//然而将这两句代码写进case语句中,来解决这个问题,那么每
//个case语句中都要写进这两句代码,那么就会形成代码冗余
switch (input)
{
case 1:
ret = add(x,y);
printf("%d\n",ret);
break;
case 2:
ret = sub(x,y);
printf("%d\n",ret);
break;
case 3:
ret = mul(x,y);
printf("%d\n",ret);
break;
case 4:
ret = div(x,y);
printf("%d\n",ret);
break;
case 0:
printf("退出计算器\n");
break:
defalut:
printf("选择错误\n");
break;
}
} while (input);
}
上述代码中的问题,可以通过函数指针数组来解决
#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 input = 0;
int x = 0;
int y = 0;
int ret = 0;
//这里使用函数指针数组
int(*pfArr[])(int ,int) = {0, add, sub, mul, div};
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if(input == 0)
{
printf("退出计算器\n");
}
if(input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d %d", &x,&y);
ret = pfArr[input](x,y);
printf("%d\n", ret);
}
}
while(input);
}
7、指向函数指针数组的指针
//函数指针
int (*pf)(int , int);
要将多个函数指针存起来,这个时候就需要函数指针数组
//函数指针数组
int (*pfArr[4])(int,int);
要取出函数指针数组的地址
//用指针ptr来接收
ptr = &pfArr
int (*(*ptr)[4])(int,int) = &pfArr;
//ptr表示的是函数指针数组的指针
//ptr的指针类型是int (*(*)[4])(int ,int)