16.数组、指针、结构体与函数

一、数组与指针

系统需要提供一定量连续的内存来存储数组中的各个元素,内存都有地址,指针变量就是存放地址的变量,如果把数组的地址赋给指针变量,就可以通过指针变量来引用数组。在 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+iarray+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;
}
相关推荐
FeboReigns2 小时前
C++简明教程(文章要求学过一点C语言)(1)
c语言·开发语言·c++
FeboReigns2 小时前
C++简明教程(文章要求学过一点C语言)(2)
c语言·开发语言·c++
_小柏_3 小时前
C/C++基础知识复习(43)
c语言·开发语言·c++
yoyobravery3 小时前
c语言大一期末复习
c语言·开发语言·算法
落羽的落羽7 小时前
【落羽的落羽 C语言篇】自定义类型——结构体
c语言
Kisorge8 小时前
【C语言】代码BUG排查方式
c语言·开发语言·bug
yoyo勰9 小时前
sqlite3
c语言·sqlite
就爱学编程9 小时前
重生之我在异世界学编程之C语言:数据在内存中的存储篇(上)
c语言·数据结构
意疏9 小时前
【C 语言指针篇】指针的灵动舞步与内存的神秘疆域:于 C 编程世界中领略指针艺术的奇幻华章
c语言·开发语言·指针
带多刺的玫瑰10 小时前
Leecode刷题C语言之考场就座
c语言·前端·javascript