美文网首页c++精进
《C++ Primer Plus》:函数

《C++ Primer Plus》:函数

作者: Dragon_boy | 来源:发表于2020-07-28 16:05 被阅读0次

    本章内容概览

    • 基本知识
    • 函数原型
    • 按值传递函数参数
    • 函数和数组
    • const指针参数
    • 函数和字符串
    • 递归
    • 指向函数的指针

    函数基本知识

    要使用C++函数必须完成以下工作:

    • 提供函数定义
    • 提供函数原型
    • 调用函数

    void函数:

    void functionName(parameterList)
    {
         statements
    }
    

    有返回值函数:

    typeName functionName(parameterList)
    {
        statements
        return value;
    }
    

    返回值可以是整数、浮点数、指针、结构和对象等。

    函数原型

    函数原型在C++中是必须的,可以不提供变量名,有类型就够了:

    double cube(double);
    

    原型确保以下几点:

    • 编译器正确处理函数返回值
    • 编译器检查使用的参数数目是否正确
    • 编译器检查使用的参数类型是否正确。不正确则转化为正确的类型(如果可能)

    注意,仅当有意义时,原型化才会导致类型转化(参数),例如:原型不会将整数转化为指针或结构。

    函数参数和按值传递

    接受传递值的变量称为形参,传递给函数的值称为实参。在函数调用时,计算机为函数中声明的变量分配内存,函数结束时自动释放,这被称为局部变量。

    一个函数原型:

    void fifi(float a, float b);
    

    之后调用:

    float c= 1.0f;
    float d = 1.0f;
    fifi(c, d)
    

    c,d按形参位置传递给函数a,b。

    函数和数组

    将数组作为形参的函数原型:

    int sum_arr(int arr[], int n);
    

    arr是一个指针。

    之前说过,C++将数组名解释为其第一个元素的地址,即函数传递的参数是一个地址,也就是形参类型为指针,所以上述原型等价于:

    int sum_arr(int* arr, int n);
    

    在C++中的函数头和函数原型中,int*等价于int[],但在函数体中并不等价。

    如果我们调用上述函数:

    int a[10] = {};
    sum_arr(a, 10);
    

    我们将数组首地址传递给函数,即将该地址赋予arr指针。这与常规变量不同,常规变量传给函数的是其拷贝,而这里是传地址,也就是该函数可以修改真的数组。

    不过由于可以真的修改原数组,所以这种做法可能有风险,比如我们不想修改数组里的元素,只想使用的话,可以使用const 限定符来定义函数原型:

    void show_array(const double ar[], int n);
    

    如果在函数体中试图修改传入的数组,编译器就会报错。而函数原型将const double[]解释为const double* ar

    const和指针

    比如,我们声明一个指向常量的指针pt:

    int age = 39;
    const int* pt = &age;
    

    该声明指出,pt指向一个const int(39),所以不能使用pt来修改该值,也就是*pt的值为const,不能修改:

    *pt += 1;  //invalid
    cin >> *pt;  //invalid
    

    这样就很微妙,我们可以直接修改age的值,但不能通过pt来修改age的值:

    *pt = 20;  //invalid
    age = 20;  //valid
    

    我们向下面这样声明指针变量:

    const float g_earth = 9.80;
    const float* pe = &g_earth;  //valid
    
    const float g_moon = 1.63;
    float* pm = &g_moon;  //invalid
    

    第一种可行,但第二种不行。第一种情况,既不能直接修改g_earth的值,也不能通过pe修改。第二种情况下,如果将g_moon地址赋给pm,则可以使用pm修改g_moon的值,这样的话g_moon的const限定符就没意义了,所以C++禁止这种做法。

    如果将指针指向指针,这会更复杂。如果是一级间接关系,将非const指针赋给const指针是可以的:

    int age = 39;
    int* pd = &age;
    const int* pt = pd;  
    

    但二级间接关系不行,如果可以进入二级间接关系,就可以可以这么做:

    const int** pp2;
    int *p1;
    const int n =13;
    pp2 = &p1;  //假设可以
    **pp2 = &n;  //valid
    *p1 = 10;  //valid
    

    上述代码将p1地址赋给const pp2,因此可以用p1来修改const数据,这不安全,所以不能这么使用。

    将指针参数声明为指向常量数据的指针的理由(即使用const):

    • 可以避免由于无意间修改数据而导致的编程错误。
    • 使用const使得函数能够处理const和非const实参,否则只能接受非const实参。

    函数和二维数组

    将二位数组作为函数形参的形式:

    int sum(int (*ar2)[4], int size);
    

    括号是必须的,表明参数是指针而不是数组,即一个指向由4个int组成数组的指针,下面的是不行的:

    int *ar2[4]
    

    上述声明是一个由4个指向int的指针组成的数组。
    等价原型:

    int sum(int ar2[][4], int size);
    

    形参的指针类型声明了列数,size中指定了行数。

    函数和C风格字符串

    表示字符串的三种方式:

    • char数组
    • 用引号括起的字符串常量
    • 被设置为字符串的地址的char指针

    这三种类型都是char*,所以在函数中作为形参时可以直接这么声明:

    void int c_in_str(char* str);
    

    同时可以使用const限定符:

    void int c_in_str(const char* str);
    

    函数无法返回字符串,但可以返回字符串的地址:

    char* buildstr(const char*str);
    

    函数和结构体

    结构体可以直接作为变量类型在函数中使用:

    struct travel_time
    {
        int hours;
        int mins;
    };
    
    travel_time sum(travel_time t1, travel_time t2);
    

    这时候传入函数的实参是结构体的副本,返回值也是。

    不过还可以传递结构体的地址作为实参,声明函数时形参是结构体指针,同时应该使用const限定符:

    void show_polar(const polar* pda);
    

    使用函数时:

    show_polar(&polar1);
    

    函数和string对象

    string对象可以像结构体那样作为变量直接传入,如果要使用多个字符串,可以声明一个string数组:

    void display(const string sa);
    void display(const string sa[], int n);
    

    递归

    函数自己调用自己即递归,例子:

    void recurs(argumentList)
    {
        statements1
        if (test)
            recurs(arguments)
        statements2
    }
    

    递归一定要有出口条件,否则会无限递归。

    函数指针

    和数据项一样,函数也有地址。

    比如编写要估算某种代码执行的时间的函数estimate(),要完成以下工作:

    • 获取函数的地址
    • 声明一个函数指针
    • 使用函数指针来调用函数

    获取函数地址使用函数名即可。声明函数指针需要与原型对应,比如:

    double pam(int);
    

    针对上述原型,声明指针为:

    double (*pf)(int);
    

    这与原型方式一致,只是将函数名换为(*pf),也就是pam和(*pf)都是函数,那么pf就是函数指针。声明函数指针后,就可以将函数地址赋予它:

    pf = pam;
    

    上面的estimate()函数可以这么声明:

    void estimate(int lines, double (*pf)(int));
    

    第二个参数是一个函数指针,调用该函数:

    estimate(50, pam);
    

    我们还可以使用函数指针调用函数:

    double x = pam(4);
    double y = (*pf)(5);
    

    C++还允许这么使用:

    double y = pf(5);
    

    深入讨论函数指针

    下面一些函数原型,它们等价:

    const double* f1(const double ar[], int n);
    const double* f2(const double [], int);
    const douvle* f3(const double*,int);
    

    如果我们要定义一个函数指针指向上述三个函数之一:

    const double* (*p1)(const double*, int);
    

    还可以声明的时候初始化:

    const double* (*p1)(const double*, int) = f1;
    

    使用auto关键字会超级简单:

    auto p2 = f2;
    

    现在(*p1)(av,3)和p2(av,3)都调用指向的函数,即f1(),f2()。

    如果要使用三个函数,定义一个函数指针数组很方便:

    coust double* (*pa[3])(const double*, int) = {f1, f2, f3};
    

    运算符[]优先级高于**pa[3]表明pa是一个包含3个指针的数组。这时候就不能使用auto进行简单地类型推断了,不过定义好函数指针数组后就可以使用auto:

    auto pb = pa;
    

    要调用函数的话,可以这样:

    const double* px = pa[0](av, 3);
    const double* py = (*pb[1])(av, 3);
    

    第二种更直观。

    然后如何创建指向整个数组的指针,我们可以使用auto:

    auto pc = &pa;
    

    如果要自己声明的话,可以这么做:

    const double* (*(*pd)[3]) (const double*, int) = &pa;
    

    *pd[3]表明是一个3个指针的数组,(*pd)[3]表明是一个指向三个元素数组的指针。

    要调用函数的话,pd是一个指针,那么*pd就是数组,(*pd)[i]是数组中的元素,即函数指针,比如(*pd)[i](av, 3),更直观的方式是(*(*pd)[i])(av, 3)

    除了使用auto进行简化,C++还提供了typedef,针对类型可以使用typedef进行类型别名:

    typedef double real;
    

    对于函数指针还可以:

    typedef const double* (*p_fun)(const double*, int);
    p_fun p1 = f1;
    

    p_fun就成为了函数指针类型别名,然后可以:

    p_fun pa[3] = {f1,f2,f3};
    p_fun (*pd)[3] = &pa;
    

    相关文章

      网友评论

        本文标题:《C++ Primer Plus》:函数

        本文链接:https://www.haomeiwen.com/subject/sujxrktx.html