一、数组与指针
系统需要提供一定量连续的内存来存储数组中的各个元素,内存都有地址,指针变量就是存放地址的变量,如果把数组的地址赋给指针变量,就可以通过指针变量来引用数组。在 C 中,指针加 1 指的是增加一个存储单元。对数组而言,这意味着加 1 后的地址下一个元素的地址。
1.1、一维数组与指针
当定义一个一维数组时,系统会在内存中为该数组分配一个存储空间,其数组的名称就是数组在内存中的首地址。若再定义一个指针变量,并将数组的首地址传给指针变量,则该指针就指向了这个一维数组。
int *p;
int array[10];
p = array;
这里 array 是数组名,也就是数组的首地址,将它赋给指针变量 p,也就是将数组 array 的首地址赋给 p。我们也可以写成如下形式:
int *p;
int array[10];
p = &a[0];
上面的语句是将数组 array 中的首个元素的地址赋给指针变量 p。由于 array[0] 的地址就是数组的首地址。
#include <stdio.h>int main(void)
{int array[] = {1,2,3,4,5,6,7,8,9};// 指向数组的指针int *p = array;int i = 0;printf("%p\n",array);printf("%p\n",p);for(i = 0; i < sizeof(array)/sizeof(array[0]); i++){printf("%d ",*(p+i));}return 0;
}
通过指针的方式来引用一维数组中的元素
p+i
与array+n
表示数组元素a[n]
的地址,即&a[n]
- 用
*(p+i)
和*(array+n)
来表示数组中的各元素
在 C 语言中可以用 array+n
表示 数组的地址,*(array+n)
表示 数组元素;指针的移动可以使用 ++
和 --
这两个运算符。array[n] 的意思是 *(array+n)。可以认为 *(array+n) 的意思是 “到内存的 array 的位置,然后移动 n 个单元,检查储存在那里的值”。
指针类型+1等同于内存地址+sizeof(数据类型);
1.2、二维数组与指针
#include <stdio.h>int main(void)
{int a[] = {1,2,3};int b[] = {4,5,6};int c[] = {7,8,9};int * array[] = {a,b,c};int i = 0, j = 0;for(i = 0; i < 3; i++){for(j = 0; j < 3; j++){//printf("%d ",array[i][j]);//printf("%d ",*(array[i]+j));// array是指针数组的首地址// *(array+i)是指针数组中保存的一维数组的地址值// *(*(array+i)+j)是指针数组中保存的一维数组中的具体元素printf("%d ",*(*(array+i)+j));}puts("");}return 0;
}
通过指针的方式来引用一维数组中的元素。
&array[m][n]
就是第 m 行 n 列元素的地址。&array[0][0] 既可以看作数组 0 行 0 列的首地址,也可以看作二维数组的首地址。array
二维数组首元素的地址(每个元素都是内含三个 int 类型元素的一维数组)。array+n
表示二维数组的第 n 个元素(即一维数组)的地址。*(array+n)
表示二维数组第 n 个元素(即一维数组)的首元素(一个 int 类型的值)的地址。*(array+n)+m
表示二维数组第 n 个元素(即一维数组)的第 m 个元素(也是一个 int 类型的值)的地址*(*(array+n)+m)
与*(a[n]+m)
表示二维数组的第 n 个一维数组元素的第 m 个 int 类型元素的值,即数组的第 n 行第 m 列的值(array[n][m])。
1.3、指针数组
指针数组,它是数组,数组的每个元素都是指针类型。它的定义格式如下:
数据类型 * 指针数组名[数组长度];
#include <stdio.h>int main(void)
{int a = 10,b = 20,c = 30;// 指针数组里面元素存储的是指针int * array[3] = {&a,&b,&c};int i = 0;printf("指针数组大小:%d\n",sizeof(array));printf("指针元素大小:%d\n",sizeof(array[0]));for(i = 0; i < sizeof(array)/sizeof(array[0]); i++){printf("%d\n",*array[i]);}return 0;
}
使用指针数组模拟二维数组:
#include <stdio.h>int main(void)
{int array1[] = {1,2,3,4,5};int array2[] = {2,3,4,5,6};int array3[] = {3,4,5,6,7};int i = 0;int j = 0;// 指针数组,存放整型指针的数组int * parray[3] = {array1,array2,array3};for(i = 0; i < 3; i++){for(j = 0; j < 5; j++){// printf("%-3d", *(parray[i] + j));printf("%-3d", parray[i][j]);}printf("\n");}return 0;
}
1.4、数组指针
数组指针,它是指针,指向数组的指针,用来存放数组的地址。它的定义格式如下:
数据类型 (*数组指针名)[数组长度];
#include <stdio.h>int main(void)
{int array[5] = {1,2,3,4,5};int i = 0;// 数组指针,存放数组的地址int (*p)[5] = &array;for(i = 0; i < 5; i++){// p指向数组,*p其实就相当于数组名,数组名又是数组首元素的地址,// 所以,*p本质上是数组首元素的地址printf("%-3d", *(*p+i));}return 0;
}
#include <stdio.h>void print(int (*p)[5], int row, int col);int main(void)
{int array[3][5] = {1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};// array表示第一行的地址,第一行的地址,是一个一维数组的地址print(array, 3, 5);return 0;
}// int (*p)[5],数组指针,指向一维数组的指针
void print(int (*p)[5], int row, int col)
{int i = 0;int j = 0;for(i = 0; i < row; i++){for(j = 0; j < col; j++){printf("%-3d", *(*(p+i)+j));}printf("\n");}
}
二、结构体数组
结构体数组与普通的数组直接的区别在于,结构体数组中的元素时根据要求定义的结构体类型,而不是基本数据类型。
定义一个结构体数组的方式与定义结构体变量的方式相同,只是将结构体变量替换为数组。定义结构体数组的一般形式如下:
struct 结构体名
{数据类型 成员1;数据类型 成员2;...数据类型 成员n;
}数组名[数组大小];
定义结构体数组也可以先声明结构体类型再定义结构体数组:
struct 结构体名 数组名[数组大小];
与初始化基本类型的数组相同,也可以为结构体数组进行初始化操作。初始化结构体数组的一般格式为:
struct 结构体名
{数据类型 成员1;数据类型 成员2;...数据类型 成员n;
}数组名[数组大小]={{值1,值2,...值n},{值1,值2,...值n},...{值1,值2,...值n}};
为数组进行初始化时,最外层的大括号表示所列出的是数组中的元素。因为每一个元素都是结构体类型,所以每一个元素也使用大括号,其中包含每一个结构体元素的成员数据。
#include <stdio.h>struct Person
{char name[20];int age;
}person[3] = {{"Sakura",10},{"Mikoto",14},{"Shana",15}};int main(void)
{int i = 0;for(i = 0; i < 3; i++){printf("name: %s,age: %d\n",person[i].name,person[i].age);}return 0;
}
三、结构体指针
3.1、指向结构体变量的指针
一个指向变量的指针表示的是变量所占内存中的起始地址。如果一个指针指向结构体变量,那么该指针指向的是结构体变量的起始地址。由于指针指向结构体变量的地址,因此它可以使用指针来访问结构体中的成员。定义结构体指针的一般格式如下:
结构体类型 *指针名;
使用指向结构体变量的指针访问成员有两种方法:
第一种方式是使用 点运算符 引用结构体成员:
(*指针名).成员名
使用点运算符的方式引用结构体成员的方式,*指针名 一定要使用括号,因为点运算符的优先级是最高的,如果不使用括号,就会先执行点运算符然后才会执行 *运算符。
第二种方式是使用 指向运算符 引用结构体成员:
指针名->成员名
#include <stdio.h>struct Person
{char name[20];char gender[10];int age;
}person={"Sakura","女",10};int main(void)
{struct Person *pPerson;pPerson = &person;printf("name: %s\n",(*pPerson).name);printf("age: %d\n",pPerson->age);return 0;
}
结构体变量名并不是结构体的地址,因此要在结构体变量名前面加上 & 运算符;
3.2、指向结构体数组的指针
结构体指针变量不但可以指向一个结构体变量,还可以指向结构体数组,此时指针变量的值就是结构体数组的首地址。结构体指针变量也可以直接指向结构体数组中的元素,这是指针变量的值就是该结构体数组元素的地址。
#include <stdio.h>struct Person
{char name[20];int age;
}person[3] = {{"Sakura",10},{"Mikoto",14},{"Shana",15}};int main(void)
{struct Person *pPerson = person;int i = 0;for(i = 0; i < 3; i++){printf("name: %s,age: %d\n",(*(pPerson+i)).name,(pPerson+i)->age);}return 0;
}
四、结构体与函数
4.1、结构体的成员作为函数参数
只要结构体成员是一个具有单个值的数据类型(即,int 及其相关类型、char、float、double 或 指针),便可把它作为参数传递给接受该特定类型的函数。使用这种方式为函数传递参数与普通的变量作为实际参数是一样的,是值传递方式。如果需要在被调函数中修改主调函数中成员的值,就要传递成员的地址。
#include <stdio.h>struct Person
{char name[20];int age;
};void printPersonInfo(char *name,int age);int main(void)
{struct Person person = {"Sakura",10};printPersonInfo(person.name,person.age);return 0;
}void printPersonInfo(char *name,int age)
{printf("name: %s\n",name);printf("age: %d\n",age);
}
传值时,实际参数要与形式参数的类型一致;
4.2、指向结构体的指针作为函数参数
在传递结构体变量的指针时,只是将结构体变量的首地址进行传递,并没有将变量的副本进行传递。由于传递的是结构体变量的地址,如果在函数中改变成员中的数据,那么返回主调函数是结构体的成员变量也会发生改变。
#include <stdio.h>struct Person
{char name[20];int age;
};void printPersonInfo(struct Person *pPerson);int main(void)
{struct Person person = {"Sakura",10};printPersonInfo(&person);return 0;
}void printPersonInfo(struct Person *pPerson)
{printf("name: %s\n",(*pPerson).name);printf("age: %d\n",pPerson->age);
}
4.3、结构体作为函数参数
使用结构体变量作为函数参数时,采用的是“值传递”,它会将结构体变量所占内存单元的内容全部顺序传递给形式参数,形式参数也必须是同类型的结构体变量。
在形式参数的位置使用结构体变量,但是函数调用期间,形式参数也要占用内存单元。这种传递方式在空间和时间上开销都比较大。另外,根据函数参数传值方式,如果在函数内部修改了变量中的成员值,则改变的值不会返回到主调函数中。
#include <stdio.h>struct Person
{char name[20];int age;
};void printPersonInfo(struct Person person);int main(void)
{struct Person person = {"Sakura",10};printPersonInfo(person);return 0;
}void printPersonInfo(struct Person person)
{printf("name: %s\n",person.name);printf("age: %d\n",person.age);
}
4.4、结构体作为函数的返回值
现在的 C(包括 ANSI C),函数不仅能把结构体作为参数传递,还能把结构体作为返回返回值返回。把结构体作为函数参数可以把结构的信息传给函数;把结构体作为函数返回值能把结构的信息从被调函数传给主调函数。
#include <stdio.h>
#include <string.h>struct Person
{char name[20];char gender[10];int age;
};struct Person getInfoByName(char *name, struct Person *persons, int length);
void showInfo(struct Person person);int main()
{struct Person persons[3] = {{"Sakura", "女", 10},{"Mikoto", "女", 14},{"Shana", "女", 15}};struct Person person = getInfoByName("Sakura", persons, sizeof(persons)/sizeof(persons[0]));showInfo(person);return 0;
}struct Person getInfoByName(char *name, struct Person persons[], int length)
{int i = 0;for(i = 0; i < length; i++){if(!strcmp(persons[i].name, name)){return persons[i];}}
}void showInfo(struct Person person)
{printf("name: %s\n", person.name);printf("gender: %s\n", person.gender);printf("age: %d\n", person.age);
}
现在的 C 允许把一个结构体赋值给另一个结构体,还可以把一个结构体初始化为相同类型的另一个结构体。
五、指针与函数
5.1、指针变量作函数参数
C 语言中的实际参数和形式变量之间的数据传递是单向的 “值传递” 方式。指针变量作为函数参数也是如此,调用函数不可能改变实际参数指针变量的值,但可以改变实际参数指针变量所指向变量的值。
#include <stdio.h>void swap(int * a,int * b);int main(void)
{int a = 10;int b = 20;printf("交换前:a = %d, b = %d\n",a,b);swap(&a,&b);printf("交换后:a = %d, b = %d\n",a,b);return 0;
}void swap(int * a,int * b)
{int temp = *a;*a = *b;*b = temp;
}
int *array 形式和 int array[] 形式都表示 array 是一个指向 int 的指针。只有在函数原型或函数定义头中,才可以用 int array[] 代替 int * array;int array[] 代表指针 array 指向的不仅仅是 int 类型值,还是一个 int 类型数组的元素。
因为数组名是该数组首元素的地址,作为实际参数的数组名要求形式参数是一个与之匹配的指针。只有在这种情况下,C 才会把 int array[] 和 int *array 解释成一样。也就是说,array 是指向 int 的指针。由于函数原型可以省略参数名,所以下面 4 种原型都是等价的;
void sum(int * array, int length);
void sum(int *, int);
void sum(int array[], int length);
void sum(int [], int);
#include <stdio.h>#define SIZE 10int sum(int array[], int length);int main(void)
{int marbles[SIZE] = {20,10,5,39,4,16,19,26,31,20};long answer;answer = sum(marbles, SIZE);printf("The total nnumber of marbels is %ld.\n", answer);printf("The size of marbles is %zd bytes.\n", sizeof marbles);return 0;
}int sum(int array[], int length)
{int i;int total = 0;for(i = 0; i < length; i++)total += array[i];printf("The size of array is %zd bytes.\n", sizeof array);return total;
}
marbles 的大小是 40 字节,这是因为marbles 内含 10 个 int 类型的值,每个值占 4 字节,所以整个 marbles 的大小是 40 字节。但是,array 才 8 字节,这是因为 array 并不是数组本身,它是一个指向 marbles 数组首元素的指针。我们的系统种用 8 字节储存地址,随意指针变量的大小是 8 字节。
5.2、指针函数
C 语言允许函数的返回值是一个指针(地址),这样的函数称为 指针函数。定义指针函数的一般格式如下:
返回值类型 * 函数名(参数列表);
#include <stdio.h>
#include <string.h>char *strLong(char *str1,char *str2);int main(void)
{char * str1 = "hello";char * str2 = "world";char *result = strLong(str1,str2);printf("%s\n",result);return 0;
}char * strLong(char * str1,char * str2)
{if(strlen(str1) > strlen(str2)){return str1;} else if(strlen(str1) < strlen(str2)){return str2;} else {return "st1 is the same length as str2";}
}
用指针作为函数返回值时,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针尽量不要指向这些数据。这里的销毁并不是将局部数据所占用的内存全部清零,而是程序放弃对它的使用权,后面的代码可以使用这块内存。
C 语言不支持在调用函数时返回局部变量的地址,如果需要可以定义局部变量为 static 变量;
#include <stdio.h>
#include <stdlib.h>int * fun(void);int main(void)
{int * p;int i = 0; p = fun();for(i = 0; i < 10; i++){printf("%d ",*(p+i));}return 0;
}int * fun(void)
{static int array[10];int i = 0;for(i = 0; i < 10; i++){array[i] = rand();}return array;
}
5.3、函数指针
一个函数总是占用一段连续的内存空间,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址。把函数的这个地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是 函数指针。函数指针可以简单的理解为指向函数的指针。函数指针的定义格式如下:
数据类型 (*函数指针名称)(参数列表);
其中,数据类型为函数指针指向的函数返回值类型。参数列表为函数指针指向的函数的参数列表,函数的参数列表可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称。
#include <stdio.h>int max(int a, int b);int main(void)
{int num1 = 0,num2 = 0;int maxNum = 0;/*** 定义函数指针* 函数指针的名字是 pmax* int 表示该函数指针指向的函数是返回int类型的* (int,int)表示该函数指针指向的函数形参是接收两个int* 在定义函数指针时,也可以写上形参名int (*pmax)(int a,int b) = &max;* 对于函数来说,&函数名和函数名都是函数的地址*/int (*pmax)(int,int) = max;printf("请输入两个数,中间以空格分隔:\n");scanf("%d%d",&num1,&num2);/*** (*pmax)(num1,num2)通过函数指针去调用函数* 也可以这样调用pmax(num1,num2);*/maxNum = (*pmax)(num1,num2);printf("the max num is : %d\n",maxNum);return 0;
}int max(int a, int b)
{return a>b ? a : b;
}
5.4、回调函数
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。简单的来讲,回调函数是由别人的函数执行时调用你传入的函数(通过函数指针完成)。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的以放调用的,用于对该事件或条件进行响应。
#include <stdio.h>
#include <stdlib.h>void initArray(int *array,int arraySiZe,int (*fun)(void));
int getNextRandomValue(void);int main(void)
{int array[10];int i;/*** 调用initArray函数* 传入一个函数名getNextRandomValue,getNextRandomValue是一个地址,需要函数指针接收*/initArray(array,10,getNextRandomValue);for(i = 0; i < 10; i++){printf("%d ",array[i]);}return 0;
}/*** 回调函数* fun就是一个函数指针,它可以接收的函数是返回int,没有形参的函数* fun在这里被initArray调用,充当了回调函数的角色
*/
void initArray(int * array,int arraySiZe,int (*fun)(void))
{int i = 0;for(i = 0; i < arraySiZe; i++){array[i] = fun(); // 通过函数指针调用了getNextRandomValue函数}
}int getNextRandomValue(void)
{return rand(); // rand()系统函数,会返回一个随机的整数
}
六、数组、指针与函数
6.1、函数指针数组
函数指针也是指针,把函数指针放在数组中,其实就是函数指针数组。简单的说,函数指针数组就是存放函数指针的数组。
#include <stdio.h>double add(double x, double y);
double sub(double x, double y);
double mul(double x, double y);
double div(double x, double y);int main(void)
{// double (*pf)(double, double) = add; // pf是函数指针double (*operation[4])(double, double) = {add, sub, mul, div}; // operation是函数指针的数组int i = 0, result = 0;for(i = 0; i < 4; i++){result = operation[i](8,4);printf("%d\n",result);}return 0;
}double add(double x, double y)
{return x + y;
}double sub(double x, double y)
{return x - y;
}double mul(double x, double y)
{return x * y;
}double div(double x, double y)
{return x / y;
}
6.2、指向函数指针数组的指针
指向函数指针数组的指针是一个指针,它指向一个数组,数组的元素都是函数指针。
#include <stdio.h>double add(double x, double y);
double sub(double x, double y);
double mul(double x, double y);
double div(double x, double y);int main(void)
{// 函数指针数组double (*pfArr[4])(double, double) = {add, sub, mul, div};// 指向函数指针数组的指针double (*(*ppfArr)[4])(double, double) = &pfArr;int i = 0, result = 0;for(i = 0; i < 4; i++){result = (*(*ppfArr+i))(8,4);printf("%d\n",result);}return 0;
}double add(double x, double y)
{return x + y;
}double sub(double x, double y)
{return x - y;
}double mul(double x, double y)
{return x * y;
}double div(double x, double y)
{return x / y;
}