本章学习:函数、结构体
一、了解函数
在C语言中,函数的作用就是用来实现某一个功能的封装;函数是实现模块化结构的重要途经。在C语言中,有两个函数;第一就是常见的main()函数,程序的入口,一个C语言程序有且只有一个main()函数,C程序总是从main()函数开始执行,main()函数可以调用其他子函数,子函数之间又可以相互调用任意次。本次讲解的就是子函数的编写,一个函数包含着几部分:函数返回类型、函数名、传入参数形式;
格式:返回值类型 函数名(传入参数数据类型 形参名)
{ 函数体 };
比如,float get_max(float a,int b);这样子,其中有几点说明
(1) 返回值数据类型可以是void,无返回值;有返回值的类型需要指明具体的类型。比如 int、float、char 等。如果省略,默认为int类型。如果返回值类型非 void,但被调函数中没有 return 语句,函数会返回一个不确定的值。
(2)函数名属于标识符。要遵循标识符的命名规则,需要是下划线和字母开头;
(3)形参类型,也可以是无返回值,也就是无参函数,在调用无参函数时,主调函数不向被调用函数传递数据。但函数名后的()不能省略。
举例:get_max():立即终止程序的执行,不接受任何形参。
有参函数,在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据。函数参数为多个参数时,其间用逗号隔开。
举例:add(int m,int n),strcmp(const char *str1, const char *str2)
有返回值传递的函数,关于return 语句有几点说明return语句的作用:① 结束函数的执行 ②将函数运算的结果返回。
return语句后面就不能再写其它代码了,否则会报错。(与break、continue情况类似)
下面分两种情况讨论:
(1)情况1:返回值类型不是void时,函数体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。
(2)情况2:返回值类型是void时,函数体中可以没有return语句。如果要用return语句提前结束函数的执行,那么return后面不能跟返回值,直接写return; 就可以。
C程序中的所有函数都是互相独立的。一个函数并不从属于另一个函数,即函数不能嵌套定义。
同一个程序中函数不能重名,函数名用来唯一标识一个函数。即在标准的 C 语言中,并不支持函数的重载。什么是函数的重载? 函数的重载是一种编程语言特性,像C++、Java等语言都支持。它允许在同一个作用域内(比如同一个"类"中)定义多个函数名相同但参数列表不同(即参数个数不同或参数类型不同)的函数。此时的多个函数彼此构成重载。调用时,编译器会根据传递的参数类型和数量来确定调用哪个函数。如果想在 C 中模拟函数的重载有两种方式。不过,都不如 C++ 中的函数重载那样灵活和方便。
方式1:使用不同的函数名来区分不同的函数
方式2:为函数添加后缀来表示不同的版本。
二、函数的使用
在C程序中,除了main()函数是程序的入口,无需调用,由于程序总是先运行 main() 函数,所以其它函数都必须在main()之前声明。其他的自己建立或者引用标准库的函数都需要调用才能实现我们自己想要的效果;其中标准库的介绍以及在第一篇文章介绍了,需要已经封装的函数可以查阅对应的标准库,在我们编写的函数,调用时候需要声明一下,分两种情况:
(1)调用标准库,直接在头文件声明;(2) 编写的函数块,需要在头部声明;
比如:int add(int a,int b);//此类型形式就叫声明
调用时候,需要将返回值类型去掉,直接传入所需要的形式参数即可;
sum=add(12,34);//就能实现将12+34的值,存储在sum变量里面;
上面就实现了一次的函数调用,无返回值时就无需变量进行赋值操作。
2.1 C语言必懂含义---形参、实参
形参(formal parameter): 在定义函数时,函数名后面括号()中声明的变量称为形式参数,简称形参。形参只是一个形式,在调用之前并不分配内存。函数调用时,系统为形参分配内存单元,然后将主调函数中的实参传递给被调函数的形参。被调函数执行完毕,通过return语句返回结果,系统将形参的内存单元释放。**实参(actual parameter):**在调用函数时,函数名后面括号()中使用的值/变量/表达式称为实际参数,简称实参。
2.2 参数传递机制1:值传递
值传递,又称传值方式、数据复制方式,就是把主调函数的实参值复制给被调用函数的形参,使形参获得初始值。接着在函数内对形参值的修改,不影响实参值,值传递,是单向传递,只能把实参的值传递给形参,而不能把形参的值再传回给实参。
2.3 参数传递机制2:地址传递
地址传递,又称传地址方式、地址复制方式、指针传递,就是把实参地址常量进行复制,传送给形参。默认传递地址的类型:指针、数组。实参将地址传递给形参,二者地址值相同。比如1:当指针作为函数的形参时,实参传递给形参的是地址,在函数中通过形参保存的地址访问实参,进而在函数中通过地址对实参的修改影响到实参的值。这也称为双向传递。比如2:当传递数组首元素地址时,即把实参数组的起始地址传递给形参。这样形参和实参数组就占用了共同的存储空间。在被调函数中,如果通过形参修改了数组元素值,调用函数后实参数组元素值也发生相应变化。
三、结构体
C 语言内置的数据类型,除了几种原始的基本数据类型,只有数组属于复合类型,可以同时包含多个值,但是只能包含相同类型的数据,实际使用场景受限。所以C语言提供了结构体。使用结构体,内部可以定义多个不同类型的变量作为其成员。C 语言提供了 `struct`关键字,允许自定义复合数据类型,将不同类型的值组合在一起,这种类型称为结构体(structure)类型。
建立结构体格式:
struct 结构体名{
数据类型1 成员名1; //分号结尾
数据类型2 成员名2;
......
数据类型n 成员名n;
}; //注意最后有一个分号
cs
#include<stdio.h>
struct Student{ // 定义结构体:学生
int id; //学号
char name[20]; //姓名
char gender; //性别
char address[50]; //家庭住址
};
int main()
{
return 0;
}
3.1 声明结构体变量并调用成员
定义了新的数据类型以后,就可以声明该类型的变量,这与声明其他类型变量的写法是一样的。
(1)在建立结构体后面直接声明变量,就是在最后一个花括号外面声明;
(2)在main函数后面采用struct 结构体类型名称 结构体变量名进行声明;
调用成员方式:
结构体变量名.成员名 = 常量或变量值;
cs
#include <stdio.h>
#include <string.h>
struct Student{ // 定义结构体:学生
int id; //学号
char name[20]; //姓名
char gender; //性别
char address[50]; //家庭住址
}; //}stu1;声明的是结构体别名,并不是变量名;声明可以省略struct 直接就stu1 ss;
int main() {
struct Student stu1; //声明结构体变量
struct Student stu = {1001, "Tony", 'M',"重庆市渝北区街道"};//或者也可以直接初始化变量
//调用结构体成员,
stu1.id = 1001;
//stu1.name = "Halibut"; //报错,不能直接通过赋值运算符来给字符数组赋值
strcpy(stu1.name, "Tony");//需要采用函数进行复制操作字符串,Strcpy(变量,内容);
stu1.gender = 'M';
strcpy(stu1.address, "重庆市渝北区街道");
printf("id = %d,name = %s,gender = %c,address = %s\n",
stu1.id, stu1.name, stu1.gender, stu1.address);
return 0;
}
(1)先声明了一个 struct Student类型的变量 stu1,这时编译器就会为 stu1 分配内存,接着就可以为 stu1 的不同属性赋值。可以看到,struct 结构的属性通过点( . )来表示,比如 id 属性要写成 stu1.id。(2)字符数组是一种特殊的数组,直接改掉字符数组名的地址会报错,因此不能直接通过赋值运算符来对它进行赋值。可以使用字符串库函数 strcpy()来进行字符串的复制操作。(3)对于声明后初始化变量的需要一个说明大括号里面的值的顺序,必须与 struct 类型声明时属性的顺序一致。此时,可以为每个值指定属性名。同样,初始化的属性少于声明时的属性,剩下的那些属性都会初始化为 0 。
typedef struct {
int phone_no;
double minutes_of_charge;
} Phone,*pPhone;
这里多了个*pPhone,其实在定义一个结点指针p时,Phone *p;等价于 pPhone p; 前者的写法类似于int *a、char *b等更方便记忆,不必再加个pPhone p来增加记忆负担。所以我们不采用这种方法,统一删掉 *pPhone的写法。
说明:
1、在创建一个结构体变量后,需要给成员赋值。在没有给成员赋值的情况下调用,打印的值是垃圾数据,可能导致程序异常终止。
2、不同结构体变量的成员是独立的,互不影响,一个结构体变量的成员更改,不影响另外一个。
cs
//练习:盒子案例
/*(1)编程创建一个Box结构体,在其中定义三个成员表示一个立方体的长、宽和高,长宽高可以通过控制台输入。
(2)定义一个函数获取立方体的体积(volume)。
(3)创建一个结构体,打印给定尺寸的立方体的体积。*/
#include <stdio.h>
// 1. 定义Box结构体
struct Box {
double length;
double width;
double height;
};
// 2. 获取立方体体积的函数
double getVolume(struct Box box) {
return box.length * box.width * box.height;
}
int main() {
// 3. 创建结构体实例
struct Box box;
printf("输入长度:");
scanf("%lf", &box.length);
printf("输入宽度:");
scanf("%lf", &box.width);
printf("输入高度:");
scanf("%lf", &box.height);
// 调用函数获取体积并打印
printf("体积为: %.2lf\n", getVolume(box));
return 0;
}
3.2 结构体数组
结构体数组:数组元素是结构体变量而构成的数组。先定义结构体类型,然后用结构体类型定义数组变量。方式1:先声明一个结构体类型,再用此类型定义结构体数组;
直接声明:结构体类型 数组名数组长度;
方式一:
struct Person{
char name20;
int age;
};
struct Person pers3; //pers是结构体数组名
方式二:
struct Date{
int year;
int month;
int day;
}dates110,dates210;
声明了结构体数组变量,怎么初始化呢?
struct Student{ // 定义结构体:学生
int id; //学号
char name20; //姓名
char gender; //性别
int age; //年龄
};
struct Student stus3 = {
{1001,"Tom",'M', 14},
{1002, "Jerry", 'M', 13},
{1003, "Lily",'F',12}
};//初始化结构体数组元素时,也可以不指定结构体数组的长度。系统在编译时,会自动根据初始化的值决定结构体数组的长度。
调用成员也与上面单个变量一致:结构体数组名下标.成员名 或者 指针->成员名
四、联合体(共用体)
有时需要一种数据结构,不同的场合表示不同的数据类型。比如,如果只用一种数据结构表示学生的"成绩",这种结构就需要有时是整数(80、90),有时是字符('A'、'B'),又有时是浮点数(80.5、60.5)。C 语言提供了共用体类型(Union 结构),用来自定义可以灵活变更的数据结构。它内部可以包含各种属性,**但同一时间只能有一个属性,因为所有属性都保存在同一个内存地址,后面写入的属性会覆盖前面的属性。**这样做的最大好处是节省内存空间。"共用体"与"结构体"的定义形式相似,但它们的含义是不同的。结构体变量所占内存长度是各成员占的内存长度之和;每个成员分别占有其自己的内存单元。共用体变量所占的内存长度等于最长的成员的长度;几个成员共用一个内存区。
格式:定义与结构体一致
union 共用体类型名称{
数据类型 成员名1;
数据类型 成员名2;
...
数据类型 成员名n;
};
调用共体成员也一样,直接变量名.成员的形式;
五、总结
本章围绕 C 语言中函数、结构体和联合体三块内容展开,核心目标是把零散的数据和逻辑组织成可复用、可维护的模块。函数负责"封装行为",结构体负责"组织数据",联合体负责"节省内存",三者配合使用,才能写出结构清晰的 C 程序。