一、数组与指针
系统需要提供一定量连续的内存来存储数组中的各个元素,内存都有地址,指针变量就是存放地址的变量,如果把数组的地址赋给指针变量,就可以通过指针变量来引用数组。在 C 中,指针加 1 指的是增加一个存储单元。对数组而言,这意味着加 1 后的地址下一个元素的地址。
1.1、一维数组与指针
当定义一个一维数组时,系统会在内存中为该数组分配一个存储空间,其数组的名称就是数组在内存中的首地址。若再定义一个指针变量,并将数组的首地址传给指针变量,则该指针就指向了这个一维数组。
c
int *p;
int array[10];
p = array;
这里 array 是数组名,也就是数组的首地址,将它赋给指针变量 p,也就是将数组 array 的首地址赋给 p。我们也可以写成如下形式:
c
int *p;
int array[10];
p = &a[0];
上面的语句是将数组 array 中的首个元素的地址赋给指针变量 p。由于 array[0] 的地址就是数组的首地址。
c
#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、二维数组与指针
c
#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、指针数组
指针数组,它是数组,数组的每个元素都是指针类型。它的定义格式如下:
c
数据类型 * 指针数组名[数组长度];
c
#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;
}
使用指针数组模拟二维数组:
c
#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、数组指针
数组指针,它是指针,指向数组的指针,用来存放数组的地址。它的定义格式如下:
c
数据类型 (*数组指针名)[数组长度];
c
#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;
}
c
#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");
}
}
二、结构体数组
结构体数组与普通的数组直接的区别在于,结构体数组中的元素时根据要求定义的结构体类型,而不是基本数据类型。
定义一个结构体数组的方式与定义结构体变量的方式相同,只是将结构体变量替换为数组。定义结构体数组的一般形式如下:
c
struct 结构体名
{
数据类型 成员1;
数据类型 成员2;
...
数据类型 成员n;
}数组名[数组大小];
定义结构体数组也可以先声明结构体类型再定义结构体数组:
c
struct 结构体名 数组名[数组大小];
与初始化基本类型的数组相同,也可以为结构体数组进行初始化操作。初始化结构体数组的一般格式为:
c
struct 结构体名
{
数据类型 成员1;
数据类型 成员2;
...
数据类型 成员n;
}数组名[数组大小]={{值1,值2,...值n},
{值1,值2,...值n},
...
{值1,值2,...值n}};
为数组进行初始化时,最外层的大括号表示所列出的是数组中的元素。因为每一个元素都是结构体类型,所以每一个元素也使用大括号,其中包含每一个结构体元素的成员数据。
c
#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、指向结构体变量的指针
一个指向变量的指针表示的是变量所占内存中的起始地址。如果一个指针指向结构体变量,那么该指针指向的是结构体变量的起始地址。由于指针指向结构体变量的地址,因此它可以使用指针来访问结构体中的成员。定义结构体指针的一般格式如下:
c
结构体类型 *指针名;
使用指向结构体变量的指针访问成员有两种方法:
第一种方式是使用 点运算符 引用结构体成员:
c
(*指针名).成员名
使用点运算符的方式引用结构体成员的方式,*指针名 一定要使用括号,因为点运算符的优先级是最高的,如果不使用括号,就会先执行点运算符然后才会执行 *运算符。
第二种方式是使用 指向运算符 引用结构体成员:
c
指针名->成员名
c
#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、指向结构体数组的指针
结构体指针变量不但可以指向一个结构体变量,还可以指向结构体数组,此时指针变量的值就是结构体数组的首地址。结构体指针变量也可以直接指向结构体数组中的元素,这是指针变量的值就是该结构体数组元素的地址。
c
#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 或 指针),便可把它作为参数传递给接受该特定类型的函数。使用这种方式为函数传递参数与普通的变量作为实际参数是一样的,是值传递方式。如果需要在被调函数中修改主调函数中成员的值,就要传递成员的地址。
c
#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、指向结构体的指针作为函数参数
在传递结构体变量的指针时,只是将结构体变量的首地址进行传递,并没有将变量的副本进行传递。由于传递的是结构体变量的地址,如果在函数中改变成员中的数据,那么返回主调函数是结构体的成员变量也会发生改变。
c
#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、结构体作为函数参数
使用结构体变量作为函数参数时,采用的是"值传递",它会将结构体变量所占内存单元的内容全部顺序传递给形式参数,形式参数也必须是同类型的结构体变量。
在形式参数的位置使用结构体变量,但是函数调用期间,形式参数也要占用内存单元。这种传递方式在空间和时间上开销都比较大。另外,根据函数参数传值方式,如果在函数内部修改了变量中的成员值,则改变的值不会返回到主调函数中。
c
#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),函数不仅能把结构体作为参数传递,还能把结构体作为返回返回值返回。把结构体作为函数参数可以把结构的信息传给函数;把结构体作为函数返回值能把结构的信息从被调函数传给主调函数。
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 语言中的实际参数和形式变量之间的数据传递是单向的 "值传递 " 方式。指针变量作为函数参数也是如此,调用函数不可能改变实际参数指针变量的值,但可以改变实际参数指针变量所指向变量的值。
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 种原型都是等价的;
c
void sum(int * array, int length);
void sum(int *, int);
void sum(int array[], int length);
void sum(int [], int);
c
#include <stdio.h>
#define SIZE 10
int 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 语言允许函数的返回值是一个指针(地址),这样的函数称为 指针函数。定义指针函数的一般格式如下:
c
返回值类型 * 函数名(参数列表);
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 变量;
c
#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、函数指针
一个函数总是占用一段连续的内存空间,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址。把函数的这个地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是 函数指针。函数指针可以简单的理解为指向函数的指针。函数指针的定义格式如下:
c
数据类型 (*函数指针名称)(参数列表);
其中,数据类型为函数指针指向的函数返回值类型。参数列表为函数指针指向的函数的参数列表,函数的参数列表可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称。
c
#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、回调函数
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。简单的来讲,回调函数是由别人的函数执行时调用你传入的函数(通过函数指针完成)。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的以放调用的,用于对该事件或条件进行响应。
c
#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、函数指针数组
函数指针也是指针,把函数指针放在数组中,其实就是函数指针数组。简单的说,函数指针数组就是存放函数指针的数组。
c
#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、指向函数指针数组的指针
指向函数指针数组的指针是一个指针,它指向一个数组,数组的元素都是函数指针。
c
#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;
}