前言
【重拾C语言】五、模块化程序设计——函数(定义、调用、参数传递、结果返回、函数原型;典例:打印字符图形、验证哥德巴赫猜想)_QomolangmaH的博客-CSDN博客
https://blog.csdn.net/m0_63834988/article/details/133580009?spm=1001.2014.3001.5501 前文介绍了函数的基础知识,包括如何定义函数、函数的调用形式和过程、函数结果的返回以及函数原型的使用等。本文将再论函数,主要介绍指针、数组、结构体等作参数;函数值返回指针、结构体,以及C语言作用域相关知识。
九、再论函数
9.1 参数
9.1.1 参数的传递规则
C语言只有一种参数类别——值参。值参意味着:
- 参数要求:赋值兼容
- 形实结合:
- 计算实参表达式的值;
- 把实参值按赋值转换规则,转换成形参的类型;
- 把转换后的实参值送入形参变量中。
- 执行函数:
- 值参表示形参本身,它是函数内的一个局部变量,已经与实参脱离关系(无关)了。在函数内对形参的赋值不影响实参。
- 结束返回: 实参值无任何变化,还是调用函数之前的值。
#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
结构体,包含两个整型成员变量x
和y
。printPoint
函数,接收一个Point
类型的结构体作为参数,并在函数内部打印结构体的坐标值。main
函数中,创建一个名为point
的Point
结构体变量,并初始化其x
和y
成员变量的值。然后,调用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
函数,将&point
(point
的地址)作为参数传递给它 - 输出结果与方法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. 返回结构体值
- 函数的结果类型是结构体类型
- 直接把一个结构体值带回调用函数的主程序
#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. 返回结构体指针
- 函数的结果类型是指向结构体类型变量的指针类型
#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)则是在函数以外定义的变量,它们不从属于任何特定的函数。全局变量的作用范围从定义处开始,一直延伸到整个源文件的结束,包括各个函数。
#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;
}