文章目录
- 字符指针
- 指针数组
- 指针数组的使用
- 数组指针
- 数组指针的使用
- 数组传参和指针传参
- 一维数组传参
- 二维数组传参
- 一级指针传参
- 二级指针传参
- 函数指针
指针的概念
- 内存单元是有编号的,编号 == 地址 == 指针
- 指针本质上是个用来存放地址的变量,地址是唯一的用来标识一块内容空间。
- 指针(变量)的大小固定为 4/8 个字节(32位系统/64位系统)。
- 指针也是有类型的,指针的类型决定好了指针 ± 整数的步长,以及指针解引用操作时的能操控的字节数。
字符指针
字符指针的形式一般为 char*
- 将一个 char 类型变量的地址赋给 char* 类型的指针。
char ch = 'a';
char* pc = &ch;
有时候也可以让字符指针指向字符串
- 字符指针指向的字符串为常量字符串。
- 字符指针此时存储的是,指向的是字符串首字符的地址。
- 字符指针指向的常量字符串不可通过指针解引用来修改。
char* p = "abcdef";//此时 p 里存着的是 a 的地址
- 因为 p 指向的是个常量字符串,为了避免指向的串被修改,指针需要加上 const。
const char* p = "abdef";//这样指向的字符串就不能通过 *p 进行修改了。
字符指针例题
- 下面代码的输出结果为什么会是这样?
- 注:代码中比较的是串之间的地址。str1234 指向的都是各个串的首元素的地址。
- str1 和 str2 分别创建了两块独立的数组空间,这两块空间的起始地址肯定不会相同,只不过这两块空间正好都存储子着 hello word! 这串数据罢了。
- 因为 str3 和 str4 指向的是同一个常量字符串,常量字符串因为没人能改它,所以这个串在内存中只会创建一份,即 str3 和 str4 指向的是同一块空间的 h 的地址。
指针数组
指针数组是数组
- 字符数组:存放字符的数组。
- 整型数组:存放整型的数组。
- 指针数字:存放指针的数组,存放在数组中得元素都是指针类型。
int* arr[5]; //存放整型指针的数组
char* str[5]; //存放字符指针的数组
指针数组的定义格式
- 存储的元素类型 数组名 [常量表达式]
int* p1[5];
- [] 操作符的优先级要比 * 运算符的优先级高,所以 数组名先和 [5] 结合,p1 就被定义为一个具有 5 个元素的数组。
- 再看 int* 说明数组 p1 里存着的 5 个是 int* 类型的指针变量。
指针数组的使用
1. 模拟二维数组
- 二维数组每行的元素个数是一样的,创建多个一维数组,将这几个一维数组当成二维数组的每一行。
- 使用指针数组存储这多个一维数组的首元素地址。
int arr1[] = {1,2,3,4};
int arr2[] = {2,2,3,4};
int arr3[] = {3,2,3,4};
int* parr[] = {arr1,arr2,arr3};//parr 数组就是指针数组
- 这样子想要拿出对应位置的元素的方式和二维数组就没两样了。
2. 指向字符指针
- 可以用来存储多个字符串的首字符地址。
数组指针
数组指针是指针
- 数组指针是一个指针,它指向一个数组。
数组名的理解
- 除了下面两个例外之外,数组名都表示的是数组首元素的地址。
- sizeof(数组名):这里的数组名表示整个数组,计算整个数组的大小。
- &数组名:这里的数组名表示取出整个数组的地址。
数组指针的定义
- 将一整个数组的地址取出来赋给数组指针。
int arr[10] = {0};
int (*parr)[10] = &arr;//parr 用来存放数组的地址,parr 就是数组指针。
- 先看 parr,数组名 parr 先和 * 结合,说明 parr 是一个指针;
- 往后一看,跟着一个 [10],说明 parr 指向的这个数组具有 10 个元素。
- 往前一看,这个 int 说明了 parr 指向的这个数组的 10 个元素每个都是 int 类型的变量。
数组指针的使用
- 一般情况下,使用数组指针去访问数组其实是很不方便的。
int arr[] = {1,2,3,4,5};
int (*p)[5] = &arr;
printf("%d\n",(*p)[2]);//*p <==> arr
- 所以,在一维数组上基本不会使用数组指针,在二维数组上才有使用数组指针的意义。
管理二维数组
- 数组名是首元素的地址,那么函数的形参就可以写成指针的形式。
- 使用数组指针做形参的时候,数组指针就是用来接收二维数组第一行的地址的。
//parr 存储第一行的地址,[4] 表示每行有 4 列
void print(int(*parr)[4],int row,int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//printf("%d ", parr[i][j]);
printf("%d ", *(*(parr + i) + j));//这两种写法等价
}
putchar('\n');
}
}
int main()
{
int arr[4][4] = { {1,2,3,4},{2,2,3,4},{3,2,3,4},{4,2,3,4} };
print(arr, 4, 4);//传第一行的地址过去
return 0;
}
分析下面代码的的意思
int (*parr[10])[5];
- parr 先和 [10] 结合,说明 parr 是一个具有 10 个元素的数组。
- 将 parr[10] 拿掉之后,剩下了 int (*) [5],这是个数组指针,该指针指向的数组具有 5 个元素,每个元素为 int。
- 所以,int (*parr[10])[5] 的意思就是,parr 是个拥有 10 个元素的数组,每个元素都是一个指向 int [5] 数组的数组指针。
数组传参和指针传参
- 很多时候,难免需要把(数组)或者(指针)作为参数传给函数,此时函数的参数又应该如何设计?
一维数组传参
- 下面代码的所有函数中,形参是否设计合理?
void test(int arr[]) //1:√
{}
void test(int arr[10]) //2:√
{}
void test(int* arr) //3:√
{}
void test2(int* arr[20])//4:√
{}
void test2(int** arr) //5:√
{}
int main()
{
int arr[10] = { 0 }; //每个元素都是 int 类型的变量
int* arr2[20] = { 0 }; //每个元素都是 int* 类型的变量
test(arr);
test2(arr2);
return 0;
}
- 数组传参,形参的是可以写成数组形式的。
- 数组传参,作为形参的数组是可以写上数组大小的,反正也不会去用它。
- 数组传参的本质是传递数组首元素的地址,所以形参可以是指针类型。
- 数组传参,传了个指针数组给函数,自然可以用指针数组的形式接收。
- 传了个首元素的地址过去,arr2 的首元素是个 一级指针 ,一级指针的地址自然得用二级指针接收。
二维数组传参
void test(int arr[3][5]) //1:√
{}
void test(int arr[][]) //2:×
{}
void test(int arr[][5]) //3:√
{}
void test(int* arr) //4:×
{}
void test(int* arr[5]) //5:×
{}
void test(int(*arr)[5]) //6:√
{}
void test(int** arr) //7:×
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
- 传递二维数组过去,自然可以用二维数组的形式接收。
- 二维数组作为参数不能和正常的定义二位数组的形式一样,只能省略行,不能省略列。
- 二维数组作为形参,可以省略行数。
- 二维数组的首元素是第一行的地址,也就是传了个一维数组的地址给函数,不能用 int* 这种整形指针接收一个数组的地址。
- arr 是个指针数组,不能用来接收传过来的第一行的地址。
- arr 是个数组指针,可以用来接收传过来的二维数组第一行的地址。
- arr 是个二级指针,只能接收一级指针的地址,不能用来接收数组的地址。
一级指针传参
- 一级指针传参时,形参部分写成一级指针即可。
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]);
print(p, sz);//一级指针p,传给函数
return 0;
}
二级指针传参
void test(int** ptr) //ptr 是用来接收一级指针的地址的
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp); //将 pp 里存着的 p 的地址传过去
test(&p); //直接将 p 的地址传过去
return 0;
}
函数指针
- 指向函数的指针,称之为函数指针。
函数指针的定义格式
返回类型 (*指针变量名)(指向的函数的参数类型)
如何得到函数的地址?
- &函数名就是函数的地址;
- 函数名也是函数的地址。
- 函数名 == &函数名 == 函数的地址。
定义函数指针
int (*pf)(int,int) = &add;
int (*pf)(int,int) = add;
//这两种形式都能将 add 的地址赋给函数指针 pf
- pf 先和 * 结合,说明 pf 是个指针;
- 第一个 int 表示 pf 指向的函数的返回类型是个 int;
- (int,int) 表示 pf 指向的函数的形参为两个 int 类型。
函数指针的使用
- 对函数指针解引用就相当于找到了原来的函数,
- 如:*pf 等价于 add。
- 所以也可以使用函数指针来调用函数。