C++基础 (第六章:函数与结构体)

函数

这一章,我们将向大家介绍 C++ 语言中,函数的概念、定义与调用。

在数学中,也有 函数 这个概念,但它与程序设计中的概念完全不同。

但在程序设计中,所谓“函数”,指的是一个事先定义好的代码模块,一经定义,就可以在程序的多个位置重复使用——比如我们之前使用的 sort 函数等,就是 C++ 各个库中已经定义好的函数

函数的定义

我们已经知道,在 C++ 语言中,任何变量在使用之前首先都要进行声明。同样地,任何函数在使用之前也要先完成定义。

简单地说,你要让编译器知道,你定义的这个函数里面究竟有什么代码需要执行,然后编译器才能在你调用函数的时候,知道接下来该做什么。

对于如下的函数定义:

void fun(int n) {
    cout<<"Hello";
}

其中,fun为函数名,int n为形式参数表。形式参数表 说明了函数输入的每个参数的类型和名称,其声明方式与变量的声明类似。内部的两重循环为函数体语句,在这部分语句中,可以用形式参数表中的变量(如变量n)进行各种操作。

我们一般将void fun(int n) {}称为函数的头部。

如果有多个形式参数(简称为 形参),则用逗号分隔即可,每个形式参数的类型都要明确写出,不能省略:

void fun(int a, int b, int c) {}

形参的取值决定于调用这个函数的代码。例如,我们可以在主函数调用刚刚定义的输出三角形的output函数:

output(3);
output(4);
output(5);

每次调用的代码output(3)中的 33 就是 实际参数(简称 实参)。当我们指定实际参数为 33 之后,output函数的函数体语句中就可以认为形参变量n的初始值为 33,也就可以输出一个大小为 33 的直角三角形了。

函数的形参一定要和实参一一对应,参数个数和对应位置的参数类型完全相同。

数组参数

当我们需要使用数组作为参数时,可以这么写:

void fun(int a[],int n){
    for(int i=0;i<n;i++){
        cout<<a[i]<<" ";
    }
}

无参数函数

当一个函数没有任何形参时,可以按照如下的方式定义:

void fun(){
    int x;
    cin>>x;
    cout<<x;
}
int main(){
    fun();
    return 0;
}

无论是函数的定义还是调用,函数名后的一对小括号都是必不可少的。如果不加这对小括号,会导致编译错误哦。

函数内修改参数

在函数内修改形参的值,是不一定会对实参的值产生影响的。比如下面这段代码:

void fun(int x){
    x++;
}
int main(){
    int a=5;
    add(a);  //a==5
    return 0;
}

执行add(a)之后,a的值仍然为 55 而不是 66。对于intstringdoublechar等类型的形参,都是如此。

函数的返回值

在前面的课程中,我们已经向大家介绍了函数的定义和调用,在每次定义函数的开头,都会写上一个 void,void表示没有返回值

一个函数除了可以执行一系列指定的逻辑外,还可以有一个返回值。在函数定义的结构中:

[返回值类型] 函数名(参数列表){
    函数体
}

除了void以外,还可以在返回值类型处填写intdouble等类型,表示有返回值。

注意:返回值类型是由函数类型决定的,返回值返回给函数调用去了

return 语句

如果一个函数的返回值类型不为void但没有return语句,则会返回一个不固定的值,所以千万不要漏掉return语句哦。

如果返回值类型为void,那么可以不写return语句,也可以在函数体内部写return语句。例如:

void fun(int n){
    for(int i=2;i<=n;i++){
        if(n%i==0){
            cout<<i<<endl;
            return;
        }
    }
}

这个函数能够输出n的最小的大于 11 的因子。这种情况下的return后面不要接任何值,当执行到return语句后,会跳出所在的min_factor函数。当定义的函数返回值类型不为void时,执行到中间的return语句同样会结束所在的函数,忽略函数体内之后的所有语句。

main 函数

现在,学习了函数与返回值的相关知识,让我们再回过头来看看 C++ 程序的主体结构:

int main(){

    return 0;
}

实际上,这个main就是一个函数——称为 C++ 程序的 主函数。一个 C++ 程序由一个主函数和若干个子函数组成——主函数是程序执行的开始点和结束点。在主函数中,return 0;语句将结束主函数,在子函数中,return相关语句会结束子函数。在主函数中,我们可以调用定义好的各种子函数,而子函数又可以再调用其他的函数。

根据我们已经学过的知识,我们不难发现,main函数一般没有参数(但在某些情况下也可以有参数),返回值为整数类型,一般情况下在函数的最后一句,我们会将0作为main函数的返回值,并结束main函数的执行。这个返回值最后会传递给操作系统,而返回值为0代表程序没有错误,正常结束——如果程序出错,最后就会以其他返回值结束(具体返回值是多少取决于错误类型)。

基本函数

在数学函数库中,还有 四舍五入函数 round、上取整函数 ceil 和下取整函数 floor,它们都接受一个(双精度)浮点数作为传入的参数,需使用头文件#include<cmath>

round(2.6)=3;

还有次方计算函数 pow,开根号函数 sqrt,绝对值计算函数 fabs (对于浮点数求绝对值的函数),abs 对整数求绝对值的函数。

pow(2,2)=4;

max 函数和 min 函数,要求两个参数类型必须一致。

max(1,2)=2;

sizeof

sizeof 的括号中可以是变量类型,也可以是某个变量,最终的结果都是该数据类型在计算机内存中所占的字节数。

sizeof(int)=4;

memset

memset 函数可以帮我们对数组进行初始化,但是请记得,memset 函数的初始化并不是将数组中的每个元素进行赋值,而是对字节进行赋值,如果不理解这个概念,没关系。

memset 函数需要头文件#include<cstring> 

变量

在前面的课程里,我们已经学习过了各种各样的变量 —— 在自定义函数、主函数内部的变量;循环头部的控制变量;全局变量;自定义函数的形式参数,等等。

我们通常将函数内定义的变量称为局部变量。变量可以根据定义的位置,分为如下三类:

  • 局部变量
  • 全局变量
  • 函数形参

当我们声明一个变量后,这个变量能够被使用的范围,我们称它为变量的作用域

变量的规范使用

外层循环里的变量a的作用域为红色框,内层循环里的变量a的作用域为蓝色框。在蓝色框内,同时有外层和内层两个变量a,应该以谁为准呢?

当出现内外层代码块定义了名字相同的变量时,应该以最内层的代码块中定义的变量为准。

因此,程序不会编译错误,而是会输出5

但是,当在同一个代码块的同一层内定义了名字相同的变量后,就会提示“定义了相同的变量”哦。

建议还是不要写出上面这种代码。为了避免出错,除了循环的控制变量以外,尽量不要起名字相同的变量

结构体

我们在之前的课程中学习了数组,我们可以声明一个 int a[10] 的数组,在其中包含 10 个整数型的元素。通过数组,我们可以管理和使用这些类型一致的数组元素。

C++ 语言为我们提供了一种被称为 结构体(structure) 的方式,通过定义一个结构体,我们可以将一系列类型相同或不同的元素放在一起。例如,如果我们希望定义一种存放个人基本信息的结构,我们就可以通过定义结构体的关键字struct完成这一过程:

struct Person{
    string name;    // 姓名
    int age;        // 年龄
    char gender;    // 性别
    float height;   // 身高
};

在这样一个被定义的结构体类型中,我们包含了姓名(字符数组)、年龄(整数型)、性别(字符型)、身高(浮点型)。

当我们定义了person结构体类型后,如果我们需要保存tom的信息,我们就不再需要分别声明四个不同类型变量,而只需要使用结构体类型直接声明结构体变量:

Person tom;

结构体类型生成的结构体变量中的元素可以通过成员运算符 .进行访问。被访问的结构体变量的元素可以被视为任何一个普通变量,我们可以通过 cin 读入值到结构体变量的元素、也可以通过 cout 输出它的值。例如,对于上面已有的结构体变量tom我们可以进行如下的赋值:

Person tom;
tom.name = "Tom";
tom.age = 54;
tom.gender = 'm';
tom.height = 170.18f;

我们可以看到,无论是结构体中哪一个成员元素变量,都需要在结构体变量名后加上成员运算符后才可以被访问。结构体变量名加上成员运算符 . 再加上成员元素名作为一个整体,在被使用时与一个普通的变量完全一致。

结构体 在这样一个被定义的结构体类型中,我们包含了姓名(字符数组)、年龄(整数型)、性别(字符型)、身高(浮点型)。 当我们定义了person结构体类型后,如果我们需要保存tom的信息,我们就不再需要分别声明四个不同类型变量,而只需要使用结构体类型直接声明结构体变量: Person tom;

我们可以看到,无论是结构体中哪一个成员元素变量,都需要在结构体变量名后加上成员运算符后才可以被访问。结构体变量名加上成员运算符 . 再加上成员元素名作为一个整体,在被使用时与一个普通的变量完全一致。

结构体初始化

当然,我们也可以通过类似于数组初始化的方式直接在声明结构体的时候,就让其中的成员元素获得初值。

Person tom={ "Tom",54,'m',170.18f};

当用于初始化的信息特别长时,我们也可以分多行来写:

Person tom={
    "Tom",
    54,
    'm',
    170.18f
};

结构体初始化 当然,我们也可以通过类似于数组初始化的方式直接在声明结构体的时候,就让其中的成员元素获得初值。 Person tom = { "Tom" , 54 , 'm' , 170.18f }; 当用于初始化的信息特别长时,我们也可以分多行来写: Person tom = { "Tom" , 54 , 'm' , 170.18f };

结构体数组

结构体其实就是自定义类型,那么它既然是一种类型,那么它就跟intdoublechar 这些类型是一样的。

struct student{
    string name;
    int id;
};

所以我们可以定义整型变量 int a,我们当然也可以定义结构体类型的变量 student a

我们可以定义整型数组 int a[10],我们当然也可以定义结构体类型的数组 student a[10]

整型数组由整数型变量构成,而结构体数组则由结构体变量构成。

所以两者之间在概念和定义上,并没有任何区别。

结构体的构造函数

在语法上,构造函数具有这样的性质:

  • 函数名与结构体名完全相同
  • 不能定义返回值类型,也不能有return语句
  • 可以有形参,也可以没有形参,可以带有默认参数
  • 可以重载

在使用的时候,我们不需要手动调用构造函数。当我们创建结构体的时候,构造函数会自动被调用。

你可能已经发现了一个问题——之前我们在定义 Student 结构体的时候并没有定义构造函数,那么这个时候,Student 结构体生成的对象又该如何进行初始化呢?事实上,如果我们定义一个结构体的时候,不声明任何构造函数,那么编译器在编译的时候,就会为我们自动生成一个默认构造函数,它具有这样的特点:

  • 参数列表为空,不为数据成员赋初值
  • 如果结构体内定义了成员的初始值,则使用结构体内定义的初始值
  • 如果没有定义结构体内的初始值,则以默认方式初始化
  • 基本类型的数据默认初始化的值是不确定的(类似于我们在主函数中声明一个变量却不赋初始值的情况)

简而言之,这样一个构造函数,它的特点就是“什么都不做”,单纯只是创建一个结构体而已——相当于这样的一个形式:

student(){
}

需要注意的是,如果我们在结构体中已经定义了一个构造函数(可以是任意形式)的话,那么编译器就不会再为我们定义默认构造函数了——这个时候,如果我们需要使用到默认构造函数的话,不要忘记自己再定义它。

如上所示,初始化列表的写法是,在构造函数的括号后面加一个冒号,然后按照成员变量(参数)的格式,依次对每一个变量进行初始化,彼此之间用逗号隔开。

这里一定要注意以下两点:

  • 函数的参数列表 绝对不能省略,像Student():name(n), score(s)这样的写法是不允许的。

  • 如果在初始化完成员变量之后,还有别的事情要做,那可以把代码写在大括号里。但是,就算之后什么都不做,也必须写大括号——大括号不能省略!!!

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值