【重拾C语言】九、再论函数(指针、数组、结构体作参数;函数值返回指针、结构体;作用域)

2024-07-30 08:52:41 浏览数 (2)

前言

【重拾C语言】五、模块化程序设计——函数(定义、调用、参数传递、结果返回、函数原型;典例:打印字符图形、验证哥德巴赫猜想)_QomolangmaH的博客-CSDN博客

https://blog.csdn.net/m0_63834988/article/details/133580009?spm=1001.2014.3001.5501 前文介绍了函数的基础知识,包括如何定义函数、函数的调用形式和过程、函数结果的返回以及函数原型的使用等。本文将再论函数,主要介绍指针、数组、结构体等作参数;函数值返回指针、结构体,以及C语言作用域相关知识。

九、再论函数

9.1 参数

9.1.1 参数的传递规则

C语言只有一种参数类别——值参。值参意味着:

  • 参数要求:赋值兼容
  • 形实结合:
    • 计算实参表达式的值;
    • 把实参值按赋值转换规则,转换成形参的类型;
    • 把转换后的实参值送入形参变量中。
  • 执行函数:
    • 值参表示形参本身,它是函数内的一个局部变量,已经与实参脱离关系(无关)了。在函数内对形参的赋值不影响实参。
  • 结束返回: 实参值无任何变化,还是调用函数之前的值。
代码语言:javascript复制
#include <stdio.h>

void square(int num) {
    num = num * num;  // 修改形参的值
    printf("Inside the function: %dn", num);
}

int main() {
    int number = 5;
    printf("Before the function call: %dn", number);
    square(number);  // 调用函数并传递实参
    printf("After the function call: %dn", number);
    
    return 0;
}
9.1.2 指针作参数

一般意义上,如果函数的形参是指针类型,对应调用时,相应实参也应是指针类型表达式

代码语言:javascript复制
#include <stdio.h>

void square(int *num) {
    *num = (*num) * (*num);  // 修改形参指针所指向的值
    printf("Inside the function: %dn", *num);
}

int main() {
    int number = 5;
    printf("Before the function call: %dn", number);
    square(&number);  // 调用函数并传递实参的地址
    printf("After the function call: %dn", number);
    
    return 0;
}

输出:

使用指针作为形参来传递实参的地址。在函数内部,通过解引用指针并修改指针所指向的值,实现了对实参的修改。

9.1.3 数组作参数

在C语言中,数组名实际上是一个指针,表示数组首元素的地址。因此,当将数组名作为实参传递给函数时,实际上传递的是数组名的指针值。

在函数调用时,数组名作为实参传递给函数的形参,只传递了数组名的值,也就是数组的首地址。函数内部并不会为形参开辟数组的存储空间,而只会为形参开辟一个指针变量的空间,用于存储传递进来的实参数组的地址。

代码语言:javascript复制
#include <stdio.h>

void printArraySize(int arr[]) {
    printf("Size of the array inside the function: %lun", sizeof(arr));
    for (int i = 0; i < 5; i  ) {
        printf("%d ", arr[i]);
    }
    printf("n");
}

int main() {
    int array[] = {1, 2, 3, 4, 5};
    printf("Size of the array in the main function: %lun", sizeof(array));
    printArraySize(array);

    return 0;
}

输出:

将数组array作为实参传递给printArraySize函数,可以看到,在printArraySize函数内部,sizeof(arr)的结果是8(64位系统上),而不是数组的实际大小。这是因为在函数调用过程中,只传递了数组名的指针值,而不是整个数组的值。

如上述代码所示,数组作为形参时,可以省略数组形式参数最外层的尺寸

错误示例:

代码语言:javascript复制
void printMatrix(int matrix[][], int rows)
代码语言:javascript复制
void printMatrix(int matrix[3][], int rows)
代码语言:javascript复制
void printMatrix(int matrix[3][][3], int rows)

正确示例:

代码语言:javascript复制
#include <stdio.h>

void printMatrix(int matrix[][3], int rows) {
    for (int i = 0; i < rows; i  ) {
        for (int j = 0; j < 3; j  ) {
            printf("%d ", matrix[i][j]);
        }
        printf("n");
    }
}

int main() {
    int matrix[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int rows = sizeof(matrix) / sizeof(matrix[0]);
    
    printf("Matrix elements in the main function:n");
    printMatrix(matrix, rows);
    
    return 0;
}
9.1.4 结构体作参数
a. 直接用结构体变量作函数参数
代码语言:javascript复制
#include <stdio.h>

struct Point {
    int x;
    int y;
};

void printPoint(struct Point p) {
    printf("Point coordinates: (%d, %d)n", p.x, p.y);
}

int main() {
    struct Point point = {3, 5};

    printf("Point coordinates in the main function:n");
    printPoint(point);

    return 0;
}
  • Point结构体,包含两个整型成员变量xy
  • printPoint函数,接收一个Point类型的结构体作为参数,并在函数内部打印结构体的坐标值。
  • main函数中,创建一个名为pointPoint结构体变量,并初始化其xy成员变量的值。然后,调用printPoint函数,将point作为参数传递给它。
  • 输出:
b. 用指向结构体变量的指针作函数参数
代码语言:javascript复制
#include <stdio.h>

struct Point {
    int x;
    int y;
};

void printPoint(struct Point* p) {
    printf("Point coordinates: (%d, %d)n", p->x, p->y);
}

int main() {
    struct Point point = {3, 5};

    printf("Point coordinates in the main function:n");
    printPoint(&point);

    return 0;
}
  • printPoint函数,接收一个指向Point类型结构体的指针作为参数
  • main函数中,调用printPoint函数,将&pointpoint的地址)作为参数传递给它
  • 输出结果与方法a相同:

9.2 函数值

9.2.1 返回指针值

函数可以返回指针作为其返回值,这样可以在函数外部访问函数内部创建的变量或数据。

函数返回类型不允许是数组类型和函数类型、共用体类型,除此之外允许一切类型, 当然允许指针类型,带回指针值的函数的函数定义说明符形式是:

代码语言:javascript复制
类型名 *函数名( 形参列表 )
代码语言:javascript复制
#include <stdio.h>

int* getArray() {
    static int arr[] = {1, 2, 3, 4, 5};
    return arr;
}

int main() {
    int* ptr = getArray();
    
    for (int i = 0; i < 5; i  ) {
        printf("%d ", *(ptr   i));
    }
    
    return 0;
}

输出:

9.2.2 返回结构体值

函数的计算结果可能是一个结构体值。在C语言中,有两种途径能够把该结构体值通过函数名字带回到主调函数。

a. 返回结构体值
  • 函数的结果类型是结构体类型
  • 直接把一个结构体值带回调用函数的主程序
代码语言:javascript复制
#include <stdio.h>

struct Point {
    int x;
    int y;
};

struct Point createPoint(int x, int y) {
    struct Point point;
    point.x = x;
    point.y = y;
    return point;
}

int main() {
    struct Point p = createPoint(3, 4);
    
    printf("Point coordinates: (%d, %d)n", p.x, p.y);
    
    return 0;
}
b. 返回结构体指针
  • 函数的结果类型是指向结构体类型变量的指针类型
代码语言:javascript复制
#include <stdio.h>
#include <stdlib.h>

struct Point {
    int x;
    int y;
};

struct Point* createPoint(int x, int y) {
    struct Point* point = malloc(sizeof(struct Point));
    point->x = x;
    point->y = y;
    return point;
}

int main() {
    struct Point* p = createPoint(3, 4);
    
    printf("Point coordinates: (%d, %d)n", p->x, p->y);
    
    free(p);
    
    return 0;
}
  • 函数createPoint()接受两个参数,并动态分配内存来创建一个Point类型的结构体变量。然后,它将给定的坐标值分配给结构体的成员,并返回指向该结构体的指针。
  • main()函数中,调用createPoint()函数来创建一个Point结构体,并使用指针访问结构体的成员来打印坐标值。最后,使用free()函数释放了动态分配的内存,以避免内存泄漏。

9.3 作用域

9.3.1 局部量和全局量
  • 局部变量(Local Variables)是在函数内定义的变量,包括形参。它们的作用范围限定在所属的函数内部。另外,定义在复合语句内部的变量的作用范围则限定在该复合语句内部。
  • 全局变量(Global Variables)则是在函数以外定义的变量,它们不从属于任何特定的函数。全局变量的作用范围从定义处开始,一直延伸到整个源文件的结束,包括各个函数。
代码语言:javascript复制
#include <stdio.h>

// 全局变量
int globalVariable = 10;

void myFunction()
{
    // 局部变量
    int localVar = 20;
    
    printf("局部变量:%dn", localVar);
    printf("全局变量:%dn", globalVariable);
}

int main()
{
    myFunction();
    
    // 尝试访问局部变量和全局变量
    // 这里不能访问局部变量localVar,会导致编译错误
    printf("全局变量:%dn", globalVariable);
    
    return 0;
}
9.3.2 作用域

作用域是指在程序中标识符有效的区域。在C语言中,每个源程序编译单位(例如源文件),每个函数定义、函数原型以及复合语句都构成一个作用域区域。在一个标识符的作用域内,可以使用该标识符,并且使用的是相应声明的标识符。这意味着在不同的作用域中可以使用相同名称的标识符,因为它们处于不同的作用域,互相之间不会产生冲突。

a. 文件作用域(全局作用域)

在函数之外定义的标识符具有文件作用域,它们在整个源文件中可见,在文件中的任何位置都可以使用这些标识符。

b. 函数作用域

在函数内部定义的标识符具有函数作用域,它们只在函数内部可见,在函数外部无法使用这些标识符。

c. 块作用域

在复合语句(代码块)内部定义的标识符具有块作用域,它们只在该代码块内可见。当代码块执行完毕后,其中定义的标识符就不再可见。

d. 函数原型作用域

函数原型中声明的标识符具有函数原型作用域,它们只在函数原型所在的作用域中可见。函数原型作用域主要用于函数声明中的参数。

e. 代码示例
代码语言:javascript复制
#include <stdio.h>

// 文件作用域(全局作用域)
int globalVariable = 10;

void function1()
{
    // 函数作用域
    int localVariable = 20;
    printf("Local variable: %dn", localVariable);
    printf("Global variable: %dn", globalVariable);
}

void function2()
{
    // 函数作用域
    int localVariable = 30;
    printf("Local variable: %dn", localVariable);
    printf("Global variable: %dn", globalVariable);
}

int main()
{
    // 块作用域
    {
        int blockVariable = 40;
        printf("Block variable: %dn", blockVariable);
    }

    function1();
    function2();

    return 0;
}

0 人点赞