• 第08章 函数探幽


    <c++ primer plus>第六版

    8 函数探幽

    8.1 c++内联函数

    内联函数是c++为提高程序运行速度所做的改进.
    常规函数与内联函数之间的区别, 不在于编写方式, 而在于c++编译器如何将它们组合到程序中.

    例: 一般定义内联函数时, 省略函数原型, 直接在原型处定义整个函数.
    inline double square(double x) {return x*x;}

    c语言中使用#define来提供宏, 可以实在原始的内联代码

    #define SQUARE(X) X*X
    
    a = SQUARE(5.0); //编译时替换为 a = 5.0*5.0, 相当于内联函数.
    

    8.2 引用变量

    引用变量是c++新增的一种复合类型.
    引用是已定义变量的别名.
    引用的主要用途是用途函数的形参.

    int rats = 100;
    int & rodents = rats;
    //rodents 是 rats的别名.
    //int & 可以认为是指向int的引用.
    //&是类型标识运算符的一部分(不是地址运算符),
    //rats和rodents的值和地址都相同.
    
    cout << "rats    = " << rats << endl;
    cout << "rodents = " << rodents << endl;
    cout << "addr rats    = " << &rats << endl;
    cout << "addr rodents = " << &rodents << endl;
    
    
    
    int rats = 101;
    int & rodents = rats; //引用, rodents相当于rats, &rodents相当于&rats.
    int * rodents = &rats;//指针, *parts 相当于rats, prats   相当于&rats.
    

    引用与指针区别:

    1. 引用在声明时必须初始化, 指针可以先声明再赋值. 可以认为引用接近于const指针.
    int rats = 101;       //一个int型变量
    int & rodents = rats; //rodents是rats的别名, 它的值是101.
    
    int bunnies = 50;     //又一个int型变量
    rodents = bunnies;    //什么效果? 1) [错]rodents变成了bunnies的引用 2) [对]rodents仍然是rats的引用, 仅仅是值变成了50, 相当于rats=bunnies.
    
    

    8.2.2 将引用用作函数参数

    引用用作函数参数, 使得函数中的变量名成为实际参数的别名, 这种方法称为按引用传递.

    #include<iostream>
    void swap_r(int &a, int &b); //a和b是引用
    void swap_p(int *p, int *q); //p和q是指针
    void swap_v(int  a, int  b); //a和b是新变量
    int main()
    {
        using namespace std;
    
        int wallet1 = 300;
        int wallet2 = 350;
    
        cout << "wallet1=" << wallet1 << ", wallet2=" << wallet2 << endl;
    
        //使用引用交换内容
        swap_r(wallet1, wallet2);
        cout << "wallet1=" << wallet1 << ", wallet2=" << wallet2 << endl;
    
        //使用指针交换内容
        swap_p(&wallet1, &wallet2);
        cout << "wallet1=" << wallet1 << ", wallet2=" << wallet2 << endl;
    
        //使用新变量交换内容
        swap_v(wallet1, wallet2); //交换失败
        cout << "wallet1=" << wallet1 << ", wallet2=" << wallet2 << endl;
    }
    
    void swap_r(int &a, int &b)
    {
        int tmp = a;
        a = b;
        b = tmp;
    }
    
    void swap_p(int *p, int *q)
    {
        int tmp = *p;
        *p = *q;
        *q = tmp;
    }
    
    void swap_v(int a, int b)
    {
        int tmp = a;
        a = b;
        b = tmp;
    }
    

    8.2.3 引用的属性和特别之处

    如下函数要求ra是一个引用, 所以现代c++不允许recube(x+3.0)这种调用, 因为x+3.0不是变量, 早期c++会创建一个临时就是, ra成为该临时变量的引用.

    double recube(double &ra);
    double recube(double &ra)
    {
        ra *= ra*ra;
        return ra;
    }
    

    8.2.4 将引用用于结构

    引用的主要作用是为了用于结构和类, 而不是基本的内置类型.

    struct free_throws
    {
        std::string name;
        int made;
        int attempts;
        float percent;
    };
    
    void set_pc(free_throws & ft); //函数参数是一个引用, 引用指向free_throws
    void display(const free_throws & ft); //函数参数是一个常引用, 不允许修改数据
    free_throws &accumulate(free_throws & target, const free_throws & source); //返回引用
    

    传统返回: 计算return后面的表达式, 得到一个值复制到临时位置, 调用函数的程序使用这个临时值.
    返回引用: 不需要将值复制到临时位置.

    注意: 如果返回引用, 则该引用不能指向临时变量.

    const free_throws & clone2(free_throws &ft)
    {
        free_throws new_guy; //临时变量, 在函数终止时会释放
        new_guy = ft;
        return new_guy; //返回临时变量的引用, 会引发错误.
    }
    

    解决上述问题有两个方法:

    1. 将引用作为参数传递给函数, 然后返回它.
    2. 使用new来分配新的内存空间, 但后续要记得使用delete来释放该内存.
    const free_throws & clone3(free_throws &ft)
    {
        free_throws * pt; //分配新内存
        *pt = ft;
        return *pt; //返回对该内存的引用
    }
    free_throws & jolly = clone3(three); //jolly变成对函数中新分配内存的引用.
    

    8.2.5 将引用用于类对象

    给函数传递类对象时, c++通常做法是使用引用.

    8.2.6 对象, 继承, 引用

    继承的一个特性是: 基类引用可以指向派生类对象, 无需进行强制类型转换.
    实际结果: 定义一个函数, 接受基类引用作为参数, 调用函数时可以传给它基类对象, 也可以传给它派生类对象.

    #include<iostream>
    #include<fstream>
    #include<cstdlib>
    
    using namespace std;
    
    void file_it(ostream &os, double fo, const double fe[], int n);
    const int LIMIT = 5;
    
    int main()
    {
        ofstream fout;
        const char *fn = "ep_data.txt";
    
        fout.open(fn);
        if (!fout.is_open())
        {
            cout << "Can't open " << fn << ". Bye." << endl;
            exit(EXIT_FAILURE);
        }
    
        double objective = 1800;
        double eps[LIMIT] = {30, 19, 14, 8.8, 7.5};
    
        file_it(fout, objective, eps, LIMIT); //第一个参数是ostream的引用, 可以传给它ofstream的引用
        file_it(cout, objective, eps, LIMIT);
    }
    
    void file_it(ostream &os, double fo, const double fe[], int n)
    {
        ios_base::fmtflags initial;
        initial = os.setf(ios_base::fixed); //存好初始格式, 定点表示法模式.
    
        os.precision(0);
        os << "Focal length of objective: " << fo << "mm" << endl;
    
        os.setf(ios::showpoint); //显示小数点模式, 当前小数部分为0.
        os.precision(1); //显示多少位小数.
        os.width(12);    //下一次输出操作使用的字段宽度(然后恢复默认).
        os << "f.1. eyepiece";
    
        os.width(15);
        os << "magnification" << endl;
    
        for (int i=0; i<n; i++)
        {
            os.width(12);
            os << fe[i];
    
            os.width(15);
            os << int( fo/fe[i] + 0.5) << endl;
        }
    
        os.setf(initial);
    }
    

    8.2.7 何时使用引用参数

    使用引用参数的主要原因:

    1. 在函数中能够修改数据对象.
    2. 传递引用省去了复制数据的时间, 可以提高程序的运行速度.

    指导原则: 只传递值而不做修改的函数中:

    1. 如果数据对象很小(内置数据类型或小型结构), 则按值传递.
    2. 如果数据对象是数组, 则使用指针传递, 并将指针声明为const指针.
    3. 如果数据对象是较大的结构, 则使用const指针或const引用, 节省复制结构所需时间和空间.
    4. 如果数据对象是类对象, 则使用const引用. 类设计的语义常常要求使用引用, 因此传递类对象参数的标准方式是按引用传递.

    指导原则: 在函数中修改数据的函数中:

    1. 如果数据对象是内置数据类型, 则使用指针.
    2. 如果数据对象是数组, 则只能使用指针.
    3. 如果数据对象是结构, 则使用指针或引用.
    4. 如果数据对象是类对象, 则使用引用.

    8.3 默认参数

    char * left(const char * str, int n=1); //原型中, 指定参数n的默认值为1
    char * left(const char * str, int n)    //函数定义中, n不指定默认值
    {
        ...
    }
    

    8.4 函数重载(函数多态)

    c++通过函数参数列表的不同来确定要使用的重载函数版本.
    函数的参数列表也称为函数特征标(function signature).
    函数的参数个数和参数类型可以作为特征标.
    函数的返回值类型不能做为特征标.

    double cube(double x)和 double cube(double &x)不能共存, 因为引用和类型本身视为同一特征标.
    匹配函数时, 非const可以转为const, 反过来不行, 所以把一个const变量传给函数的非const参数会报错.

    #include<iostream>
    
    using namespace std;
    
    void f0(char * bits);
    void f0(const char * bits);
    
    void f1(char * bits);
    void f2(const char * bits);
    
    int main()
    {
        char c0[20] = "abcdefg";
        const char c1[20] = "hijklmn";
    
        f0(c0); //非const变量, 调用重载的非const参数函数.
        f0(c1); //const变量, 调用重载的const参数函数
    
        f1(c0);
        //f1(c1); //const变量不能传给非const参数, 报错: invalid conversion from "const char*" to "char*"
    
        f2(c0); //非const变量可以传给const参数
        f2(c1);
    
        return 0;
    }
    
    void f0(      char * bits) { cout << "calling f0-no-const" << endl; }
    void f0(const char * bits) { cout << "calling f0-const" << endl; }
    void f1(      char * bits) { cout << "calling f1-no-const" << endl; }
    void f2(const char * bits) { cout << "calling f2-const" << endl; }
    

    8.5 函数模板

    函数模板是通用的函数描述, 也就是说他们使用泛型来定义函数, 其中的泛型可以用具体的类型替换.
    由于类型是用参数表示的, 因此模板特性有时也称为参数化类型.
    模板不能缩短可执行程序, 它只是使函数定义更简单可靠.

    #include<iostream>
    
    //新建一个模板, 将类型命名为T(可以任意选择), 其中template和typename是关键字.
    template <typename T> //注意这一行没有分号, 这一行与下一行是一句, 不能分开.
    void Swap(T &a, T &b)
    {
        T temp;
        temp = a;
        a = b;
        b = temp;
    }
    
    int main()
    {
        using namespace std;
    
        int i=10, j=20;
        Swap(i, j); //传给模板int类型, 编译器自动生成void Swap(int &, int &)
        cout << "i=" << i << ", j=" << j << endl;
    
        double x=24.5, y=81.7;
        Swap(x, y); //传给模板double类型, 编译器自动生成void Swap(double &, double &)
        cout << "x=" << x << ", y=" << y << endl;
    
        return 0;
    }
    

    模板重载.
    如果需要对多个不同类型使用相同算法的函数, 则使用模板.
    如果需要对多个不同类型使用不同算法的函数, 则使用模板重载.

    #include<iostream>
    
    //函数模板声明
    template <typename T> void Swap(T &a, T &b);
    template <typename T> void Swap(T * a, T * b, int n);
    
    int main()
    {
        using namespace std;
    
        int i=9, j=70;
        Swap(i, j);
        cout << "i=" << i << ", j=" << j << endl;
    
        double x[] = {1.2, 2.3, 3.4, 4.5, 5.6};
        double y[] = {9.8, 8.7, 7.6, 6.5, 5.4};
        Swap(x, y, 5);
        for (int i=0; i<5; i++)
        {
            cout << x[i] << ", " << y[i] << endl;
        }
    }
    
    //函数模板定义
    template <typename T>
    void Swap(T & a, T & b)
    {
        T temp;
        temp = a;
        a = b;
        b = temp;
    }
    
    //函数模板重载
    template <typename T>
    void Swap(T * a, T * b, int n)
    {
        T temp[n];
        for (int i=0; i<n; i++)
        {
            temp[i] = a[i];
            a[i] = b[i];
            b[i] = temp[i];
        }
    }
    

    模板局限性: 模板函数很可能无法处理某些类型. 一种解决方案, 使用重载运算符, 另一种解决方案, 为特定类型提供具体化的模板定义.

    显式具体化

    #include<iostream>
    template <typename T> void Swap(T &a, T &b);
    
    struct job
    {
        char name[40];
        double salary;
        int floor;
    };
    template <> void Swap<job>(job & j1, job & j2); //显式具体化, 交换job, 表示这是对交换job的一个具体工作方式.
    
    void show_job(const job &j1);
    
    int main()
    {
        using namespace std;
    
        int i=8, j=72;
        Swap(i, j);
        cout << "i=" << i << ", j=" << j << endl;
    
        job jx = {"namex", 100.1, 5};
        job jy = {"namey", 200.1, 6};
        Swap(jx, jy);
    
        show_job(jx);
        show_job(jy);
    }
    
    template <typename T> void Swap(T &a, T &b)
    {
        T temp;
        temp = a;
        a = b;
        b = temp;
    }
    
    template <> void Swap<job>(job &j1, job &j2)
    {
        double tmp_salary;
        tmp_salary = j1.salary;
        j1.salary = j2.salary;
        j2.salary = tmp_salary;
    
        int tmp_floor;
        tmp_floor = j1.floor;
        j1.floor = j2.floor;
        j2.floor = tmp_floor;
    }
    
    void show_job(const job &j1)
    {
        using namespace std;
        cout << "name  = " << j1.name << endl;
        cout << "salary= " << j1.salary << endl;
        cout << "floor = " << j1.floor << endl;
    }
    

    隐式实例化, 显式实例化, 显式具体化, 统称为具体化.
    它们的相同之处在于, 它们表示的都是使用的具体类型的函数定义, 而不是通用描述.

  • 相关阅读:
    Android-View动画
    Android-RemoteView-桌面小部件
    系统的Drawable(四)-LayerListDrawable
    Android-Drawable(三)
    系统的Drawable(二)-Selector
    系统的Drawable(一)
    Android View事件分发-从源码分析
    打游戏要存进度-备忘录模式
    Java 内部类.md
    docker 常用 命令
  • 原文地址:https://www.cnblogs.com/gaiqingfeng/p/16462799.html
Copyright © 2020-2023  润新知