1 指针的定义
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向 (points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
2 指针的大小
32位机器上,指针变量的大小为4个字节;64位机器上,指针变量的大小为8个字节。
①32位和64位指的是电脑管理内存的能力;
②形象的来说,32位机器拥有32根电线,每根电线具有通电(表示为1)和不通电(表示为0)两种状态,地址有32个比特位,4个字节的大小,64位机器同理。
③32位存放0和1就会有中可能,会产生个地址,标识出个字节(byte),等于4G,因此32位机器的内存大小为4G,64位机器的内存大小位8G。
3 指针类型
type+*表示指针的类型,那么不同的指针类型有什么用呢?
指针类型意义:
①指针+-整数
指针的类型决定了指针向前或者向后走一步有多大(距离)。
#include <stdio.h>
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
pc的类型为char *,因此+1向后走1个字节(字符大小),pi的类型为int *,因此+1向后走4个字节(整型大小)。
②指针的解引用
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
int *pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
pc的类型为char *,因此解引用只访问一个字节,将44变成00,pi的类型为int *类型,解引用访问4个字节,因此将四个字节的内容全部变成0。
4 指针的运算
①指针+-整数
②指针-指针
指针相减的前提是两个指针指向的是同一块空间。指针相减得到的结果是两个指针之间元素的个数(不是字节的大小)。
例:
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
③指针的关系运算
标准规定: 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
例如:提倡下面这种写法指针开始指向arr数组最后一个元素后面的位置arr[10],
int arr[10] = {0};
int *p = arr;
for(p = arr[10];p>arr[0];);
{
*--p=0;
}
但是不提倡下面这种写法
p=arr[0]进到循环执行语句后p指向了arr数组第一个元素之前的内存arr[-1],与规定相矛盾。
int arr[10] = {0};
int *p = arr;
for(p = arr[9];p>=arr[0];p--);
{
*p=0;
}
5 指针与数组
1 数组与数组名
※※※数组名代表数组首元素的地址。
※※※两种情况除外:①sizeof(数组名),此时数组名代表整个数组,求的是整个数组的大小 (字节) ②&数组名,此时数组名代表整个数组,取出的是整个数组的地址。
例1,说明数组名代表数组首元素的地址。
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
2 指针指向一维数组
p+i 指向数组 arr 下标为i的元素的位置。
*(p+i)==arr[i]
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
3 数组指针
类比①整形指针—指向整型变量的指针,存放整型变量地址的指针变量;②字符指针—指向字符变量的指针,存放字符变量地址的指针变量
●数组指针—指向数组的指针,存放的是数组的地址的指针变量。例如:int (*p)[10]—*p结合表示p为指针变量,指向的是一个大小为10个整型变量的数组。
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int* p1 = arr;
int(*p2)[10] = &arr;//把数组arr的地址赋值给数组指针变量p2
printf("%p\n", p1);//00E9FDA8
printf("%p\n", p1+1);//00E9FDAC
printf("%p\n", p2);//00E9FDA8
printf("%p\n", p2+1);//00E9FDD0
return 0;
}
arr表示数组首元素的地址,p1指向数组首元素arr[0],p+1跳过一个整型变量,指向arr[1];&arr表示整个数组的地址,p2指向整个数组,p2+1跳过整个数组。
4 指针数组
数组是存放一组相同类型元素的集合,整型数组存放一组整型变量,字符数组存放一组字符变量,因此指针数组就是存放一组指针变量的数组。例如,int *p[10]—[]的结合性高,因此p[10]结合表示p为数组名,数组内存放的元素类型为int *。
5 区分数组指针和指针数组
int arr[5]—arr是数组,有5个元素,每个元素的类型是int;
int *parr1[10]—parr1是数组,数组有10个元素,每个元素的类型是int *;
int (*parr2)[10]—parr2是指针,指针指向一个具有10个整型变量的数组;
int (*parr3[10])[5]—parr3是数组([]结合性高),数组有10个元素,每个元素的类型是int(*)[5](是指针,指针指向的是一个具有5个整形变量的数组)
6 使用数组或指针传参
●一维数组传参
#include <stdio.h>
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int *arr)//ok,指针接收存放int类型变量的地址
{}
void test2(int *arr[20])//ok
{}
void test2(int **arr)//ok,指针接收存放int *类型变量的地址
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);//传递的是数组首元素的地址,地址存放的是int类型的变量
test2(arr2);//传递的是数组首元素的地址,地址中存放的是int *类型的变量
}
●二维数组传参
void test(int arr[3][5])//ok
{}
void test(int arr[][])//no!二维数组传参不可以省略列
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素(列)。
//这样才方便运算。
void test(int *arr)//no,arr是指向整型变量的指针,本题应该用指向一维数组的指针接收
{}
void test(int* arr[5])//no,arr是数组
{}
void test(int (*arr)[5])//ok,arr是指向int[5](有5个整型变量的数组)的指针
{}
void test(int **arr)//no,arr是指向int *变量的指针
{}
int main()
{
int arr[3][5] = {0};
test(arr);
//传递的是数组首元素的地址,二维数组可以看作是一维数组的结合,
//因此二维数组的每个元素为一维数组,因此传参传的是一维数组的地址,
//一维数组地址应该用数组指针存放,类型是int (*)[],函数应该用数组指针接收
}
数组指针在二维数组中的实际应用
#include <stdio.h>
int main()
{
int i;
int j;
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
int (*p1)[5] = arr;//arr表示二维数组首元素地址,就是一维数组int[5]的地址,需要用数组指针存放
int (*p2)[5] = &arr[0];//&arr[0]是二维数组首元素的地址
for(i = 0; i < 3; i++)
{
for(j = 0; j < 5; j++)
{
printf("%d",*(*(p2+i)+j));
//p2+i是第i行地址⇿&arr[i]
//*(p2+i)是第i行一维数组⇿arr[i]⇿一维数组名表示一维数组首元素地址⇿&arr[i][0]
//*(p2+i)+j⇿arr[i]+j是第i行一维数组第j个元素的地址⇿&arr[i][j]
//*(*(p2+i)+j)⇿*(arr[i]+j)⇿*(&arr[i][j])⇿arr[i][j]
}
}
return 0;
}
●一级指针传参
#include <stdio.h>
void print(int *p, int sz)
{
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]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
●二级指针传参
void test(char **p)
{
}
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);//ok
test(ppc);//ok
test(arr);//ok,arr是数组首元素的地址,存放的是char*的变量,是二级指针
return 0;
}
6 函数指针
函数指针是指向函数的指针,存放函数的地址。函数指针最大的一个用途是实现回调函数。
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
void (*pf1)() = test;
void (*pf2)() = &test;//函数名和&函数名均表示函数的地址
printf("%p\n", pf1);
printf("%p\n", pf2);
return 0;
}
代码理解:
代码1 (*(void (*)())0)();
代码2 void (*signal(int , void(*)(int)))(int);
7 函数指针数组
类比int * arr1[5];//整型指针数组
char* arr2[10];//字符指针数组
int (*parr1[10])();//函数指针数组,数组有10个元素,每个元素的类型是int(*)()—指针,指向一个返回类型为int,参数为空的函数。用途:转移表
8 计算器—说明函数指针和函数指针数组的用途
▶基础版
//计算器
#include<stdio.h>
void menu()
{
printf("****************************\n");
printf("****** 1.add 2.sub ******\n");
printf("****** 3.mul 4.div ******\n");
printf("****** 0.exit ******\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;
int y;
int ret;
do
{
menu();
printf("请输入要进行的操作:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个整数:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("计算结果为:%d\n", ret);//语句重复过多,用函数指针数组可以简化
break;
case 2:
printf("请输入两个整数:");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("计算结果为:%d\n", ret);
break;
case 3:
printf("请输入两个整数:");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("计算结果为:%d\n", ret);
break;
case 4:
printf("请输入两个整数:");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("计算结果为:%d\n", ret);
break;
case 0 :
printf("退出计算器\n");
break;
default:
printf("输入错误,请重新输入!\n");
break;
}
} while (input);
return 0;
}
▶函数指针数组改良版
#include<stdio.h>
void menu()
{
printf("****************************\n");
printf("****** 1.add 2.sub ******\n");
printf("****** 3.mul 4.div ******\n");
printf("****** 0.exit ******\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 = 1;
int x;
int y;
int ret;
int (*p[5])(int, int) = { NULL,Add,Sub,Mul,Div };
//将p[0]赋值为空指针,使得数组下标与菜单操作对应
while(input)
{
menu();
printf("请输入要进行的操作:>");
scanf("%d", &input);
if (input <= 4 && input >= 1)
{
printf("请输入两个整数:");
scanf("%d %d", &x, &y);
ret = p[input](x, y);
printf("计算结果为:%d\n", ret);
}
else if (input == 0)
{
printf("退出计算器!\n");
}
else
{
printf("输入错误,请重新输入!\n");
}
}
return 0;
}
▶使用函数指针实现回调函数改良版
//函数指针用途--实现回调函数
#include<stdio.h>
void menu()
{
printf("****************************\n");
printf("****** 1.add 2.sub ******\n");
printf("****** 3.mul 4.div ******\n");
printf("****** 0.exit ******\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;
}
void Calc(int(*p)(int, int))
{
int x, y;
int ret;
printf("请输入两个整数:");
scanf("%d %d", &x, &y);
ret = p(x, y);
printf("计算结果为:%d\n", ret);
}
int main()
{
int input = 1;
do
{
menu();
printf("请输入要进行的操作:>");
scanf("%d", &input);
switch (input)
{
case 1:
Calc(Add);
break;
case 2:
Calc(Sub);
break;
case 3:
Calc(Mul);
break;
case 4:
Calc(Div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("输入错误,请重新输入!\n");
break;
}
} while (input);
return 0;
}
9 指向函数指针数组的指针
int (*pf)(int, int);//pf为函数指针,指向的函数返回值为int类型,有2个参数,均为int 类型;
int (*pfArr[5])(int, int);//pfArr为函数指针数组,数组有5个元素,每个元素的类型为int(*)(int, int)(函数指针,指针指向的函数返回值为int类型,有2个参数,均为int 类型)。
int(*(*p)[5])(int, int) = &pfArr;//p为指针,指向一个有5个元素的数组,存放整个数组的地址。数组的每个元素的类型是为int(*)(int, int)(函数指针,指针指向的函数返回值为int类型,有2个参数,均为int 类型)。