1.5 函数和预处理

1.5 函数和预处理

  • 函数:能完成某一独立功能的子程序模块

函数的定义和调用

  • 主调函数:调用其他函数的函数.被调函数:被其他函数调用的函数
  • 库函数:又称标准函数,是ANSI/ISO C++编译系统预先定义好的函数,程序设计的时候可以直接使用这类函数而不用重新定义.
  • 自定义函数:用户根据程序的需要,将某一个功能相对独立的程序定义成的一个函数,或将解决某个问题的算法用一个函数来组织.
1.函数的定义
  • 格式:
<函数类型> <函数名>(<形式参数表>)
{
    <若干语句>
}
  • 一个函数的定义是由函数名,函数类型,形式参数和函数体四部分组成的.
    • 函数类型:决定了函数所需要的返回值类型
      * 函数名:是一个有效的C++标准符,函数名后面跟一对圆括号,以区别变量名及其他用户定义的标识名
    • 函数的形式参数:写在括号内,参数表中可以有0个,1个或多个,但多个参数必须用逗号分隔
  • 形参:是指调用此函数所需要的参数个数和类型
  • 一般地,只有当函数被调用的时候,系统才会给形参分配内存单元,而当调用结束后,形参所占用的内存单元又会被释放
  • C++规定,凡不加类型说明的函数,一律自动按整型处理
2.函数的调用
  • 实参:调用函数时,先写函数名,然后紧跟着括号,括号里是实际调用函数所给定的参数,称为实际参数,简称实参,并与形参相对应.
  • 格式:
    <函数名>(<实际参数表>)
  • 注意:调用函数的时候,实参与形参的个数应该相等,类型应该一致,且按顺序对应,一一传递数据
3.函数的声明
  • 格式:
    <函数类型><函数名>(<形式参数表>);
  • 位置:一般放在main函数之前
  • 函数声明消除了函数定义的影响,即不管函数在哪里定义,只要在调用前进行函数的声明就可以保证函数调用的合法性
  • 函数声明时的参数名和函数定义时的参数名可以不同,且函数声明时的形参名还可以省略,但函数名,函数类型,形参类型及个数应与定义时相同.

函数的参数传递

  • C++中的每一个变量必须先定义后使用
  • 局部变量:在函数体内定义,只能在函数体内使用的变量
  • 全局变量:在函数外部定义,它能被后面的所有函数或语句调用的变量
  • C++中函数的参数传递有两种方式:一种是按值传递,一种是地址传递或引用传递.
    • 按值传递:是指当一个函数被调用时,C++根据实参和形参的对应关系将实际参数的值一一传递给形参,供函数执行时使用.函数本身不对实参进行操作,也就是说,即使形参的值在函数中发生了改变,实参的值也不会收到影响.
    • 在值传递的方式下,函数只能通过指定函数类型并在函数体中使用return来返回某一类型的数值

带默认形参值的函数

  • 在C++中,允许在函数的声明或定义时给一个或多个参数指定默认值.这样在调用时,可以不给出参数,而按指定的默认值进行工作.
  • 当函数既有原型声明又有定义时,默认参数只能在原型声明中指定.
  • 当一个函数中需要多个默认参数时,则形参分布中,默认参数应严格从右到左逐次定义和指定,中间不能跳开.
  • 当带有默认参数的函数调用时,系统按从左到右的顺序将实参与形参结合,当实参的数目不足时,系统将按同样的顺序用声明或定义中的默认值来补齐所缺少的参数.
#include <iostream>

using namespace std;

void display(int a,int b=2,int c=3)
{
    cout << "a = " << a << ", b = " << b << ",c = "<< c <<endl;
}

int main()
{

    display(1);
    display(1,5);
    display(1,7,9);
    return 0;
}

这里写图片描述
* 由于可对同一个函数的原型可作多次声明,因此在函数声明中指定多个默认参数时,可用多条函数原型声明来指定,但同一个参数的默认值只能指定一次
* 默认参数的值可以是全局变量,全局常量,甚至是一个函数,但是不能是局部变量.默认参数的函数调用是在编译时确定的,而局部变量的值在编译时无法确定

函数的递归调用

  • 递归调用:C++允许在调用一个函数的过程中出现直接调用或间接调用函数本身的情况,称为函数的递归调用.
  • 递归函数必须要有结束过程的条件,即函数不在进行自身调用,否则递归会无限制地进行下去.

内联函数

  • 内联函数:它把函数体的代码直接插入到调用处,将调用函数的方式改为顺序执行直接插入的程序代码,这样可以减少程序的执行时间,但同时增加了代码的实际长度.
  • 使用方法:与一般函数相同,只是在内联函数定义时,需要在函数的类型前面加上inline关键字
  • 内联函数的一些限制:
    • 内联函数中不能有数组定义,也不能有任何静态类型的定义
    • 内联函数不能含有循环,switch和复杂嵌套的if语句
    • 内联函数不能是递归函数
  • 内联函数一般是比较小的,经常被调用的,大多可以一行写完的函数,并常用来代替宏定义
#include <iostream>

using namespace std;

inline float fmax(float x,float y)
{
    return x>y?x:y;
}

int main()
{
    float a;
    a = fmax(5,10); //当程序编译的时候,这个语句会变成 a = 5>10?5:10;
    cout << "Max number is " << a << endl;
    return 0;
}

函数重载

  • 函数重载:是指C++允许多个同名的函数存在,但同名的各个函数的形参必须有区别:要么形参的个数不同,要么形参的个数相同,但参数类型不同
  • 重载函数必须具有不同的参数个数或不同的参数类型,只有返回值的类型不同是不行的,因为编译器无法准确地确定应调用哪个函数
  • 当函数的重载带有默认的参数时,也应该注意避免上述二义性情况
#include <iostream>

using namespace std;

int sum(int x,int y);
int sum(int x,int y,int z);
double sum(double x,double y);
double sum(double x,double y,double z);

int main()
{
    cout << sum(2,5) << endl;           // 结果为7
    cout << sum(2,5,7) << endl;         // 结果为14
    cout << sum(1.2,5.0,7.5) << endl;   // 结果为13.7

    return 0;
}

int sum(int x, int y)
{
    return x+y;
}

int sum(int x, int y, int z)
{
    return x+y+z;
}

double sum(double x, double y)
{
    return x+y;
}

double sum(double x, double y, double z)
{
    return x+y+z;
}

作用域和可见性

  • 作用域:又称作用范围,是指程序中标识符(变量名,函数名,数组名,类名,对象名等)的有效范围
  • 一个标识符能否被引用,称为标识符的可见性
  • 根据标识符的作用范围,将作用于分为5种:函数原型作用域,函数作用域,块作用域,类作用域和文件作用域
1.块作用域
  • 这里的块指的是复合语句.块作用域也称为局部作用域
  • 在块中声明的标识符,其作用域从声明处开始,一直到结束块的花括号为止.
  • 具有块作用域的变量是局部变量.即在块中定义的变量仅在块中有效,块执行后,变量被释放
  • 当标识符的作用域相同时,不允许出现相同的标识符名;当标识符具有不同的作用域时,允许标识符同名
  • 在多层次块(块的嵌套)中,外层块与内层块之间具有不同的作用域.外层块的变量可以在内层块中使用,但内存块中的变量仅能在内层块中使用.当外层块和内层块中由同名变量定义时,外层块的同名变量在内层块中不起作用
2.函数原型作用域
  • 函数原型作用域指的是在声明函数原型时所指定的参数标识符的作用范围.
  • 这个作用范围在函数原型声明中的左,右括号之间.所以函数原型中声明的标识符可以与函数定义中说明的标识符名称不同.
3.函数作用域
  • 具有函数作用域的标识符在它声明的函数内可见,但在此函数外是不可见的.
  • 在C++语言中,只有goto语句中的标号具有函数作用域.
4.文件作用域
  • 全局标识符:在函数外或用extern说明的标识符
  • 文件作用域:全局标识符的作用域.从它声明之处开始,直到文件结束一直是可见的
  • 全局的常量或变量的作用域是文件作用域,它从定义开始到源文件结束.
  • 若函数定义在后,调用在前,必须进行函数原型声明.
    若函数定义在前,调用在后,函数定义包含了函数的原型声明
  • 声明了函数原型,函数标识符的作用域是文件作用域,它从定义开始到源文件的结束
  • 在C++中,若在块作用域内使用与局部标识符同名时,则必须使用域运算符”::”来引用,且该标识符一定要是全局标识符,即它拥有文件作用域
#include <iostream>

using namespace std;

int i = 10;         // A

int main()
{
    int i = 20;     // B
    {
        int i = 5;  // C
        int j;
        ::i = ::i + 4;  // ::i是引用A定义的变量i,不是B中的i
        j = ::i + i;    // 这里不加::的i是C中定义的变量i
        cout << "::i = " << ::i << ",j = " << j << endl;
    }
    cout << "::i = " << ::i << ",i = " << i << endl;    //这里不加::的i是B中定义的变量i
    return 0;
}

存储类型

存储类型是针对变量而言的.变量的存储类型反映了变量在哪里开辟内存空间,以及占用内存的有效期限

* 在C++中,由四种存储类型:自动类型,静态类型,寄存器类型,外部类型,这些存储类型是在变量定义时指定的.
* 格式:
<存储类型> <数据类型> <变量名表>;

1.自动类型(auto)
  • 用自动存储类型声明的变量都限制在某个程序范围内使用,即为局部变量
  • 自动存储类型变量是采用动态分配放在在栈区中分配空间的.当程序超出该变量的作用域时,就释放它所占用的内存空间
  • 在C++语言中,声明一个自动存储类型的变量是在变量类型前加上关键字auto
  • 若自动存储类型的变量是在函数内或语句块中声明的,可以省略关键字auto
2.寄存器类型(register)
  • 使用关键字register声明的寄存器变量的目的是将所声明的变量放入寄存器内,从而加快程序的运行速度
  • 但在使用register声明时,若系统寄存器已经被其他数据占据,寄存器类型变量就会自动当做auto变量
3.静态类型
  • 在声明局部变量类型前加上关键字static,则定义成一个静态类型的变量.这样的变量虽然具有局部变量的作用域,但由于它是用静态分配方式在静态数据区中分配内存空间的.
  • 只要程序还在执行,静态类型变量的值就一直有效
  • 静态类型的局部变量具有局部变量的作用域,但却又有全局变量的生存期
  • 静态类型的局部变量只在第一次执行时进行初始化,因此在声明静态变量的时候一定要指定其初值,若没有指定,则编译器会将其初值指定为0
  • 在程序中声明的全局变量总是静态存储类型.若在全局变量前加上static,则说明该变量只在这个源程序文件内使用,则该变量为全局静态变量或静态全局变量
#include <iostream>

using namespace std;

void count()
{
    int i = 0;
    static int j = 0;                           //静态类型
    i++;
    j++;
    cout << "i = " << i << ",j = " << j << endl;
}

int main()
{
    count();    //i=1,j=1
    count();    //i=1,j=2   第二次执行的时候,由于j已经初始化,所以跳过static int j = 0;
    return 0;
}
4.外部类型
  • 使用关键字extern声明的变量称为外部变量,一般是指定义在本程序外部的变量.
  • 当某个变量被声明为外部变量的时候,可以直接在本程序中引用这个变量,不必再次为它分配内存
  • C++中使用外部变量的两种情况
    • 在同一个源文件中,若定义的变量使用在前,声明在后,这时在使用前要声明为外部变量
    • 当由多个文件组成一个完整的程序,且在一个源文件程序中定义的变量要被其他若干个源文件引用时,引用的文件中要用extern对该变量作外部声明
  • 可以对同一个变量进行多次extern声明.
  • 若在声明时,给一个外部变量赋初值,编译器会认为是一个具体的变量定义,而不是外部变量的声明.

编译预处理

  • C++编程时,可以在源程序中加入一些编译命令,以告诉编译器如何对源程序进行编译.因此,在源程序编译前,要先处理这些命令.所以它们被称为编译预处理
  • C++提供的预处理命令主要有三种:宏定义命令,文件包含命令,条件编译命令.这些命令以#开头,每条预处理命令必须单独占一行,结尾没有分号
1.不带参数的宏定义
  • 格式:
    #define <宏名> 定义内容
  • #define,宏名,定义内容之间必须要用空格.且一般将宏名定义成大写
  • 宏候命的内容实际上是字符串,编译器本身不对其进行任何的语法检查,仅仅用来在程序中作与宏名的简单替换
  • 宏被定义后,使用下面指令进行重新定义
    undef 宏名
  • 一个定义过的宏可以用来定义其他新的宏,但要注意其中的括号
2.带参数的宏定义
  • 格式:
    #define <宏名>(参数名表) 定义内容
#define MAX(a,b) ((a)>(b)?(a):(b))
x = MAX(3,9);//预处理后会变成下面的
x = ((3)>(9)?(3):(9));
  • 带参数的宏相当于一个函数的功能,但却比函数整洁
  • 定义带参数的宏时,宏名与左圆括号之间不能留有空格.
  • 带参数的宏内容字符串中,参数一定要加圆括号,否则不会有正确的结果
#define AREA(r) (3.14*r*r)
x = AREA(3+2);
x = (3.14*3+2*3+2);
3.文件包含命令
  • 文件包含:指将另一个源文件的内容合并到源程序中,C++语言提供了#include命令来实现文件包含的操作.
  • 格式:两种
    #include <文件名>:用于包含那些系统提供的,放在指定子目录的头文件,称为标准方式
    #include "文件名":系统现在当前工作目录中查找,称为用户方式,找不到则按标准方式查找.
  • 一条#include指令只能包含一个文件
4.条件编译命令
  • 条件编译:希望程序按一定条件去编译源文件的不同部分

1.第一种形式:

#ifdef <标识符>
    <程序段 1>    //如果标识符被#define命令定义过了,则编译程序段1,否则编译程序段2
[#else
    <程序段 2>]
#endif

2.第二种形式

#ifndef <标识符>
    <程序段 1>    //如果标识符没有被#define命令定义,则编译程序段1,否则编译程序段2
[#else
    <程序段 2>]
#endif

3.第三种形式

#if <表达式 1>
    <程序段 1>
[#elif <表达式2>
    <程序段 2>
    ...]
[#else
    <程序段 n>]
#endif
  • 以上是C++最常用的预处理命令,它们都是在程序被正常编译之前执行的,可以放在程序的任何位置,最好是程序的开头
发布了40 篇原创文章 · 获赞 55 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/A807296772/article/details/77601202
1.5