1.指针介绍
指针是内存中最小单元的编号,也就是地址
而指针变量则是一个变量,用来存储地址;
int main()
{
int a=10;
int* pa=&a;
return 0;
}
其中,pa就是一个指针变量,用来存储a的地址。
2.指针类型
浮点数类型的结果可以通过数据的存储方式进行分析。
①指针类型决定了指针在被解引用时访问的字节个数,如果是char*类型,则访问1个字节,double*类型则访问8个字节 ,虽然int*和float*都能访问四个字节,但是int *和float*不能通用,因为整形和浮点型在内存的存储方式是不一样的
②指针类型决定了指针在±1时跳过几个字节,决定了指针的步长
③void* 类型的指针是无类型的指针,可以接收任意类型的指针,但不能对其进行解引用,也不能±整数
3.野指针
指针指向的位置是不可知的
3.1野指针的成因
①指针变量没有初始化
当局部变量没有初始化时,存放的是随机值
②指针越界访问
当指针指向的的范围超出了数组的范围时,就变成了野指针
③指针指向的空间释放
局部变量a在函数调用完之后,p指向的空间已经销毁,但是仍可以通过地址找到内存中的空间,这个空间如果没用使用,则内存中还是10
在调用完test()函数时,它的函数栈帧就空了出来,在使用printf函数时,又会建立自己的函数栈帧,将上一次所建立的函数栈帧覆盖了。
④使用
在初始化指针是不知道给指针赋什么值时,一般都赋NULL,并且在使用有可能为空指针的指针时应先判断是否为空指针
3.2 规避野指针
①指针初始化
②小心指针越界
③指针指向空间释放及时置NULL
④避免范围局部变量的地址
⑤指针使用之前检查有效性
4指针运算
4.1指针±整数
4.2指针-指针
并不是所有的指针都能相减,指向同一块空间的指针才能相减,结果的绝对值是指针之间元素的个数
tips:模拟strlen函数
int my_strlen(char* str)
{
char* ret = str;
while (*str != '\0')
{
str++;
}
return str - ret;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d", len);
return 0;
}
my_strlen("abcdef")的函数调用中传递参数为字符串abcdef的首字符的地址
char* p="abcdef";
printf("%s",p);
同样这里是将字符串abcdef的首元素的地址赋给p
4.3指针的关系运算
标准规定:允许指向数组元素的指针与数组最后一个元素后面的那个内存位置的指针进行比较,但不允许与指向第一个元素之前的那个内存位置的指针进行比较
5.二级指针
int main()
{
int a = 8;
int* pa = &a;
int** ppa = &pa;
**ppa =9;
printf("%d", a);
return 0;
}
int* pa=&a; *表示pa为指针,int表示pa指向的对象为int类型
int**ppa=&pa; 后面一个*表示ppa为指针,int*表示ppa指向对象为int* 类型
二级指针是用来存放一级指针变量的地址的
6.指针数组
存放指针的数组
模拟二维数组:
int main()
{
int arr1[] = { 1,2,3,4 };
int arr2[] = { 2,3,4,5 };
int arr3[] = { 3,4,5,6 };
int* arr[3] = { arr1,arr2,arr3 };
int i,j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%d ", *(*(arr + i) + j));
printf("%d ", *(arr[i] + j));
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
*(arr+i)等价于arr[i]
7.字符指针
tips:模拟strlen函数
int my_strlen(char* str)
{
char* ret = str;
while (*str != '\0')
{
str++;
}
return str - ret;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d", len);
return 0;
}
my_strlen("abcdef")的函数调用中传递参数为字符串abcdef的首字符的地址
char* p="abcdef";
printf("%s",p);
同样这里是将字符串abcdef的首元素的地址赋给p;
字符串常量存储在静态存储区,在其中只有一份拷贝,不会出现相同常量的不同拷贝,因此p1与p2 相等,而arr1与arr2为两个不同的数组,存储在不同的地方,地址不相等
int main()
{
char* p1 = "abc";
char* p2 = "abc";
char arr1[] = "abc";
char arr2[] = "abc";
if (p1 == p2)
printf("p1=p2\n");
else
printf("p1!=p2\n");
if (arr1==arr2)
printf("arr1=arr2\n");
else
printf("arr1!=arr2\n");
return 0;
}
8.数组指针
8.1数组指针定义
int arr[8]={0};
int* p=arr;
int (*p1)[8]=&arr;
这里的*p1表示p1是指针,[8]表示p1指向的是元素个数为8的数组,int表示指向数组的元素的类型。根据各个部分的定义写出以下数组的数组指针:
char* arr[8]={0};
首先元素类型为char*,元素个数为8,则:
char* (*pc)[8]=&arr;
8.2数组名
数组名通常表示数组首元素的地址,但是有两个例外
①:sizeof(数组名),这里数组名表示的是整个数组,计算的是整个数组的大小
②:&数组名,这里的数组名表示的是整个数组,因此&数组名取出的是整个数组的地址
从运算结果可以看到,不管是数组名还是数组元素取地址后加1,均跳过4个字节;而&数组名在加1后地址跳过了0x14,也就是20个字节,跳过了一个数组大小的字节
8.3数组指针的使用
Tips:二维数组的数组名表示数组的第一行
print(arr, 3, 5);
使用指针传参来打印二维数组的元素,由于arr表示的是二维数组的第一行的地址,也就是一个一维数组的地址,需要用数组指针来接收
void print(int (*p)[5], int r, int c)
由于p是数组指针,p+1则会跳过 1个数组大小的空间;p指向数组的第一行,则p+1指向数组的第二行;p+i表示第i+1行整个数组的地址,解引用后得到第i行的数组名,再对其解引用便可得到数组元素。*(p+i)也等同于p[i]
printf("%d ", *(*(p + i) + j));
printf("%d ",p[i][j]);
void print(int (*p)[5], int r, int c)
{
int i, j;
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;
}
8.4小练习
int arr[8];
int *arr2[8];
int (*arr3)[8];
int (*arr4[8])[5];
int arr[8]; 整形数组
int *arr2[8]; 整形指针数组
int (*arr3)[8]; 数组指针
int (*arr4[8])[5]; 存放数组指针的数组
arr4[8]表示数组,能够存储8个元素,int *[5]表示数组的元素是数组指针
这个数组指针指向的数组有5个int型元素
9.数组和指针传参
9.1数组传参
int arr1[10]={0};
char* arr2[10]={0};
test(arr1);
test1(arr2);
arr1既可以看成一个数组,也可以看作是地址;即形参可以用数组,也可以用指针;
void test(int arr1[]) {}
void test(int arr1[10]) {}
void test(int* arr1) {}
arr2是一个字符指针数组;形参可以用数组,元素类型为字符指针;也可用指针,arr2表示首元素的地址,也就是字符指针的地址,arr2相当于一个二级指针;要使用二级指针来接收
void test1(char* arr2[]) {}
void test1(char* arr2[10]) {}
void test1(char** arr2) {}
int arr1[6][7]={0};
char* arr[6][7]={0};
test1(arr1);
test2(arr2);
arr1表示一个二维的整形数组,①使用数组接收时,行可以省略,列不能省略,也就是不能省略7;②使用指针接收时,由于二维数组的数组名arr1表示的是第一行的地址,类型为数组指针,因此要使用数组指针接收
void test1(int arr1[6][7]) {}
void test1(int arr1[ ][7]) {}
void test1(int arr1[][]) {}// 不能省略列,错误的
void test1(int (*arr1)[7]) {}
arr2是一个二维的字符指针数组,当使用指针接收时,要使用数组指针,且指向数字元素的类型为char*
void test2(char* arr2[][7]) {}
void test2(char* (*arr2)[7]) {}
9.2指针传参
在指针传参的过程中,实参是什么类型,形参就用什么类型的指针进行接收。
当形参采用一级指针进行接收时,实参可以是什么?
void test(int *p) {}
实参可以是一个整形变量的地址\整形指针变量\整形数组的数组名
int a=10;
int* pa=&a;
int arr[10]={0};
test(&a); test(pa); test(arr);
void test(int** p) {}
当形参采用二级指针进行接收时,实参可以是:指针变量的地址\二级指针\指针数组\三级指针解引用等
int a=10;
int* pa=&a;
int** ppa=&pa;
int* arr[10]={0};
test(&pa); test(ppa); test(arr);
10.函数指针
10.1简单介绍
①与数组不同,函数名和&函数名都是函数的地址
int Add(int x,int y)
{
return x+y;
}
int main()
{
int a=1,b=2;
Add(a,b);
int (*pf)(int,int)=Add;
int (*pf)(int,int)=&Add;
}
在将一个函数的地址赋给函数指针时,既可以使用函数名,也可以使用&函数名;函数指针的定义和数组指针的定义类似,(*pf)表示pf为指针,int (int,int)为所指向函数的返回类型和参数类型,参数名可写可不写
在使用函数指针时,可解引用也可不解引用,但是,当解引用时,必须将*和函数指针名括起来,即:(*函数指针名)
pf(a,b);
(*pf)(a,b);
*pf(a,b);//则表示对pf(a,b)的结果进行解引用
②代码扩展
(*(void (*)())0)();
上述代码是一次函数调用,将地址0强制转换为返回值为空,参数为空的函数指针类型,(可加*也可不加)调用地址0处的函数。
void (* signal(int ,void(*)(int)))(int);
上述代码是一次函数声明;这里的*signal没有括起来,因此signal不是指针,而是一个函数名,参数类型为int 和void(*)(int)类型的函数指针,返回值也为函数指针类型。对于上述这种函数,可以使用重命名进行简化:
//typedef void(*)(int) ptf_t;这种重命名方式是错误的
typedef void(*ptf_t)(int) ;
//把void(*)(int)的函数指针类型重命名为ptf_t
int main()
{
//void (* signal(int ,void(*)(int)))(int);
ptf_t signal(int ,ptf_t );
}
10.2回调函数
回调函数:通过一个函数指针调用的函数;通常将函数的地址作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,这个函数就是回调函数。
在下面例子中,Add和Sub函数分别将自己的地址以函数指针的形式传递到calcu函数中,并调用Add和Sub函数。
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 calcu(int(*frr)(int, int))
{
int x, y;
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
printf("%d\n", frr(x, y));
}
int main()
{
calcu(Add);
calcu(Sub);
}
qosrt()函数的使用
compare函数指针所指向的函数的返回值有如下要求,同时其使用的是泛型指针进行接收,因此在写自己的比较函数时,要根据自己所排序的数据的类型对形参进行强制类型转换
qsort函数默认实现的是升序排列,如果想要得到降序排列可改变返回值。
int com_int( const void* e1,const void* e2)
{
/*if (*(int*)e1 > *(int*)e2)
return 1;
else if (*(int*)e1 < *(int*)e2)
return -1;
else
return 0;*/
return *(int*)e2 - *(int*)e1;//返回使用两个数相减的结果并以此来判断大小
}
int com_float(const void* e1, const void* e2)
{
if (*(float*)e1 > *(float*)e2)
return 1;
else if (*(float*)e1 < *(float*)e2)
return -1;
else
return 0;
//return *(float*)e1 - *(float*)e2;
//在返回过程中,float型的数强制转换为整数,容易丢失精度
//浮点型数在判断大小时一般直接使用<,>,而不使用两浮点数相减
}
int main()
{
int arr[10] = {0,1,2,3,4,5,6,7,8,9 };
//int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
float arrf[] = { 9.9,8.8,7.7,6.6,5.5,4.4,3.3,2.2,1.1,13.14,5.2,5.21 };
int sz = sizeof(arr) / sizeof(arr[0]);
int szf = sizeof(arrf) / sizeof(arrf[1]);
//boubble_sort(arr, sz);
qsort(arr, sz, sizeof(arr[0]), com_int);
qsort(arrf, szf, sizeof(arrf[1]), com_float);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
for (int i = 0; i < szf; i++)
{
printf("%.2f ", arrf[i]);
}
}
11.函数指针数组
int (*arr[10])(int ,int )={0};
*arr[10]中*arr并没有括起来,arr先与[10]结合,表明arr是一个数组,存储的元素类型为 int(*)(int,int)类型的函数指针。
11.1函数指针及函数指针数组的应用
集成加减乘除的计算器:根据菜单选择加减乘除的计算方式,然后输入两个操作数进行计算
#include<stdio.h>
void menu()
{
printf("********************\n");
printf("***1.Add 2.Sub***\n");
printf("***3.Mul 4.Div***\n");
printf("***0.exit ******\n");
printf("********************\n");
}
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;
}
//计算器----使用函数指针数组进一步优化了程序,更加简洁
int main()
{
int input;
int x, y;
do
{
menu();
printf("请输入计算方式\n");
scanf("%d", &input);
int (*arr[])(int, int) = { 0,Add,Sub,Mul,Div };
//使用函数指针数组将几个函数的地址写入数组
//并且通过在前面添加0的方式让各个函数在数组中的下标与input的值一致
//使用if分支语句对input的不同值进行操作
if (input == 0)
printf("退出计算\n");
else if (input > 0 && input <= 4)
{
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
int ret = arr[input](x, y);
printf("%d\n", ret);
}
else
printf("输入错误,请重新输入\n");
}while(input);
}
11.2指向函数指针数组的指针
int (*arr[])(int, int) = { 0,Add,Sub,Mul,Div };
int(*(*parr)[5])(int,int)=&arr;
给数组指针赋值,如果是一维数组,要对数组名取地址,即&arr;表明parr是指针则需要将*parr括起来,以免与[5]结合形成数组;指针所指向的数组元素的类型为int(*)(int ,int)的函数指针,元素个数为5.