5.指针数组
5.1.指针数组的基本介绍
要让数组的元素指向 int 或其他数据类型的地址(指针)。可以使用指针数组
5.2.指针数组定义
数据类型 *指针数组名[大小];
- 例如:double *arr[7]
- 申明一个名为arr的double类型指针数组
- 其由七个double类型指针组成。因此,arr中的每个元素都是指向double类型的指针
5.3.指针数组代码示例和内存布局示意图
#include<stdio.h> void main() { int var[3] = { 10, 100, 1000 }; int i; int* ptr[3]; for (i = 0; i < 3; i++) { ptr[i] = &var[i];//将var数组元素的地址逐一赋值给相应下标的ptr指针数组 } for (i = 0; i < 3; i++) { printf("var[%d] = %d\n", i, *ptr[i]); } }
- 申明一个名为var的int类型数组;申明一个名为ptr的指针数组,其每个元素都是指向int类型的指针
- 经过第一次循环遍历后,它的每个元素被逐一赋值卫var数组的地址
- 指针数组也具有它的内存空间和地址。如图所示,0x1122、0x1126、0x112A,即是ptr指针数组的元素的地址,它们的内容分别是0x1133、0x1137、0x113B,分别对应的是var整型数组中的每个数组元素的地址
5.4.指针数组练习
请编写程序,定义一个指向字符的指针数组来存储字符串列表(四大名著书名), 并通过遍历该指针数组,显示字符串信息 (即:定义一个指针数组,该数组的每个元素,指向的是一个字符串)
#include <stdio.h> void main() { char* books[4] = { "三国演义", "西游记", "红楼梦", "水浒传" }; int i; for (i = 0; i < 4; i++) { printf("\nbooks[%d] 指向字符串是=%s %p", i, books[i], &books[i]); } }
6.指向指针的指针
6.1 基本介绍
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置
设图中的Variable的Value为int类型,图中pointer1指向的是Variable,应该申明为 int* pointer1,则pointer2应该申明为int** pointer2
6.2.多重指针(二级,三级)代码示例
- 一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
int **ptr; // ptr 的类型是 int **
2.当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符, 比如 **ptr(当一个目标值被一个指针间接指向时,访问这个值的指针前面需要加两个*)
#include <stdio.h> int main() { int var; int* ptr; //一级指针 int** pptr; //二级指针 int*** ppptr; // 三级指针 var = 3000; ptr = &var; // var 变量的地址赋给 ptr pptr = &ptr;// 表示将 ptr 存放的地址,赋给 pptr ppptr = &pptr; // 表示将 pptr 存放的地址,赋给 ppptr printf("var 的地址 = %p\nvar = %d \n", &var, var);// 0x1133 3000 printf("\nptr 的本身的地址 = %p\nptr 存放的地址 = %p\n*ptr = %d \n", &ptr, ptr, *ptr); printf("\npptr 本身地址 = %p\npptr 存放的地址 = %p\n**pptr = %d\n", &pptr, pptr, **pptr); printf("\nppptr 本身地址 = %p\nppptr 存放的地址 = %p\n***pptr = %d\n", &ppptr, ppptr, ***ppptr); return 0; }
、
3.
7.传递指针(地址)给函数
当函数的形参类型是指针类型时,是使用该函数时,需要传递指针,或者地址,或者数组给该形参
7.1.传地址或指针给指针变量
#include<stdio.h> void test(int* p) { *p += 1; } void main() { int num = 90; int* p = # test(&num); printf("num = %d\n", num); test(p); printf("num = %d", num); }
- test函数体需要传入一个int类型的指针,因此,不管是传入变量num的地址,还是指针p,都可以;变量num的地址为0x1122,指针p的内容为0x1122,因此,它们其实传入的是同一个地址
- 因为,test函数是对传入地址的内容进行改变,所以,它在执行后,虽然test栈被销毁,但是,它所改变num的值被保留了下来
7.2.传数组给指针变量
#include<stdio.h> double getAverage(int* arr, int size) { int i, sum = 0; double avg; for (i = 0; i < size; ++i) { // arr[0] = arr + 0 // arr[1] = arr + 1 个 int 字节(4) // arr[2] = arr + 2 个 int 字节(8) //... sum += arr[i];// arr[0] =>数组第一个元素的地址 arr[1] printf("\narr 存放的地址=%p ", arr); avg = (double)sum / size; return avg; } } double getAverage2(int* arr, int size) { int i, sum = 0; double avg; for (i = 0; i < size; ++i) { sum += *arr; printf("\narr 存放的地址=%p ", arr); arr++;// 指针的++运算, 会对 arr 存放的地址做修改 } avg = (double)sum / size; return avg; } int main() { /* 带有 5 个元素的整型数组 */ int balance[5] = { 1000, 2, 3, 17, 50 }; double avg; /* 传递一个指向数组的指针作为参数 */ avg = getAverage(balance, 5); /* 输出返回值 */ printf("Average value is: %f\n", avg); return 0; }
1.在main函数中,设balance数组的首地址为(0x1122)。在执行到avg = getAverage(balance, 5)时,因为double getAverage(int* arr, int size)函数需要传入一个int类型指针变量和一个int类型变量,而getAverage(balance, 5)中balance代表的是balance数组的首地址,传入getAverage数组的是一个地址,相当于指针变量
2.在getAverage函数体中,对传入的balance数组的首地址进行一系列操作,而非在函数体内复制一份balance数组并进行操作!
8.返回指针的函数
8.1.返回指针的函数的介绍
C语言允许函数的返回值是一个指针(地址),这样的函数称为指针函数
8.2.代码示例
请编写一个函数 strlong(),返回两个字符串中较长的一个
//请编写一个函数 strlong(),返回两个字符串中较长的一个 #include<stdio.h> #include<string.h> char* strlong(char* str1, char* str2) { //通过strlen函数计算字符串的长度 printf("第一个字符串的长度为%d,第二个字符串的长度为%d\n", strlen(str1), strlen(str2)); if (strlen(str1) >= strlen(str2)) {//如果字符串1的长度大于等于字符串2的长度,则返回指向字符串1的指针 return str1; } else { return str2;//else,则返回指向字符串2的指针 } } int main() { char str1[20], str2[20], * str; printf("请输入一个字符串:\n"); gets_s(str1, sizeof(str1)); printf("请输入一个字符串:\n"); gets_s(str2, sizeof(str2)); str = strlong(str1, str2);//用str字符指针指向strlong返回的指针 printf("这两个字符串中,更长的那个字符串是:%s", str); return 0; }
8.3.指针函数注意事项和细节
- 用指针作为函数返回值时需要注意,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针不能指向这些数据
- 函数运行结束后会销毁该函数所有的局部数据,这里所谓的销毁并不是将局部数据所占用的内存全部清零,而是程序放弃对它的使用权限,后面的代码可以使用这块内存
#include <stdio.h> int* func() { int n = 100;//局部变量, 在 func 返回时,就会销毁 return &n; } int main() { int* p = func(); //func 返回指针 int n; n = *p; printf("\nvalue = %d\n", n); return 0; }
函数运行结束后,仅仅是放弃了对这块内存空间的使用权限,但是,这块内存空间的内容并没有被销毁,输出这块内存空间的内容,仍然有可能是原来的内容,但是,如果在执行多条语句后,这块内存空间就有可能被别的数据给覆盖,因此,返回的指针不能指向这些数据
3.C 语言不支持在调用函数时返回局部变量的地址,如果确实有这样的需求,需要定义局部变量为 static 变量
#include <stdio.h> int* func() { static int n = 100; // 如果这个局部变量是 static 性质的,那么 n 存放数据的空间在静态数据区 return &n; } int main() { int* p = func(); //func 返回指针 int n; n = *p; printf("\nvalue = %d\n", n); return 0; }
通过static int n =100;将100存入了静态存储区,返回的是静态存储区的地址,因此,在之后的main函数的使用中,仍然可以使用静态存储区中存储的100,而静态存储区不会被其他语句所占用
9.函数指针(指向函数的指针)
9.1 基本介绍
1.一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址(可以理解为把函数名当做函数的首地址,赋给函数指针)
2.把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。
9.2.函数指针定义
returnType (*pointerName)(param list);
- returnType为函数指针指向的函数返回值类型
- pointerName为函数指针名称
- param list为函数指针指向的函数的参数列表
- 参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称
- 注意( )的优先级高于*,第一个括号不能省略,如果写作 returnType *pointerName(param list);就成了函数原型, 它表明函数的返回值类型为 returnType *(变成返回指针的函数)
9.3.函数指针代码示例
用函数指针来实现对函数的调用, 返回两个整数中的最大值
#include <stdio.h> //说明 //1. max 函数 //2. 接收两个 int ,返回较大数 int max(int a, int b){ return a>b ? a : b; } int main() { int x, y, maxVal; //说明 函数指针 //1. 函数指针的名字 pmax //2. int 表示 该函数指针指向的函数是返回 int 类型 //3. (int, int) 表示 该函数指针指向的函数形参是接收两个 int //4. 在定义函数指针时,也可以写上形参名 int (*pmax)(int x, int y) = max; int (*pmax)(int, int) = max; printf("Input two numbers:\n"); scanf_s("%d %d", &x, &y); // (*pmax)(x, y) 通过函数指针去调用 函数max maxVal = (*pmax)(x, y);//可以把这个语句中的*去掉 printf("Max value: %d pmax = %p 本身的地址=%p\n", maxVal, pmax , &pmax); return 0; }
- 申明一个名为*pmax函数返回值类型为int的函数指针,它需要指向的函数需要传入两个int类型的变量,它指向max函数
- pmax函数指针的地址为0x1133,max函数在内存空间(代码区)的首地址为0x1122,因此,它所指向的内存空间(代码区)中max函数的首地址为0x1122
- 调用函数的方式可以是(*pmax)(int)(int),也可以使(pmax)(int)(int),前提是前面必须申明过(1)中的调用函数类型