• [C++] 函数


    函数基础

    一个典型的函数定义包括以下部分:返回类型、函数名字、由0个或多个形参组成的列表以及函数体。其中形参以逗号隔开,形参列表位于一个圆括号之内,函数指向的操作在语句块内,也就是函数体。

    函数调用

    使用调用运算符来执行函数,调用运算符的形式是一对圆括号,它作用于一个表达式,该表达式是函数或者指向函数的指针,圆括号内是逗号间隔开的实参列表,我们用实参初始化函数的形参。

    函数的调用完成了两项工作:1、用实参初始化函数对应的形参;2、将控制权转移给被调用函数。

    执行函数的第一步是定义并初始化它的形参。

    形参和实参

    实参是形参的初始值,实参的类型必须与对应的形参类型匹配,且数量相同。

    int fact(int);
    
    fact(3);
    // 正确
    
    fact(3.14)
    // 正确
    // 3,14(double) ~ 3(int)

    形参列表

    1、每个形参必须有声明;

    2、每个形参的声明必须单独书写;

    3、每个形参不能同名;

    返回值类型

    1、函数的返回值类型不能是数组类型或函数类型

    2、函数的返回值类型可以是指向数组指针(或引用)或指向函数的指针。

    局部对象

    名字有作用域,对象有生命周期

    1、名字的作用域是程序文本的一部分,名字在其中可见

    2、对象的生命周期是程序执行过程中该对象存在的一段时间

    函数的形参也是局部变量。

    同时局部变量还会隐藏在外层作用域中同名的其他所有声明。

    局部变量的生命周期依赖于定义的方式(static、非static)

    自动对象

    只存在于块执行期间的对象称为自动对象。当块执行结束后,块中创建的自动对象的值就变成未定义的了。

    局部静态对象

    local static object:在程序执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。

    局部静态变量没有显式的初始化,它将执行值初始化,内置类型的局部静态变量初始化为0.

    函数声明

    函数只能定义一次,但可以声明多次。如果一个函数永远也不会用到,那么它可以只有声明没有定义

    函数原型也就是函数声明,包括函数返回类型、函数名、形参类型

    在头文件中声明函数,在源文件中定义函数。

    分离式编译

    在分离式编译中,如果我们改变了一个源文件,只需要重新编译这个改动的文件即可。

    参数传递

    每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化。

    如果形参是引用类型,它将绑定到对应的实参上也就是引用传递,否则将实参的值拷贝后赋给形参。引用形参是它对应的实参的别名。

    当实参的值被拷贝给形参时,形参和实参是两个互相独立的对象,这样的实参被值传递。

    传值参数

    值传递时,函数对形参做的所有操作都不会影响实参。

    指针形参

    指针的行为和其他非引用类型一样,当执行指针拷贝操作时,拷贝的是指针的值,拷贝之后,两个指针是不同的指针,因为指针使我们可以间接的访问它所指的对象,所以指针可以修改它所指对象的值。

    传引用参数

    引用的操作实际上是作用在引用所引的对象上。

    通过使用引用形参,允许函数改变一个或多个实参的值

    当调用含有引用形参的函数时,只需要直接传入对象而无须传递对象的地址

    通过使用引用来避免拷贝,因为拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型不支持拷贝操作。所以通过可以引用形参来访问该类型的对象,如果不需要改变引用的对象,则可以把形参定义成对常量的引用。

    如果函数无须改变引用形参的值,最好将其声明为常量的引用

    通过使用引用形参可以返回额外信息

    如果想让函数返回多个值,有以下两种方法:

    1、引用形参为我们一次返回多个结果提供了有效的途径

    2、定义一个新的数据类型,让它包含需要返回的成员。

    const形参与实参

    如果形参是const时,需要考虑关于顶层const的讨论,顶层const保证对象本身不变。

    const int ci = 42;
    // 顶层const,不能改变ci
    int i = ci;
    // 当拷贝ci时,会忽略它的顶层const
    int *const p = &i;
    // 顶层const,不能改变p
    *p = 0;
    // 可以通过p改变对象的内容是允许的

    当实参书初始化形参时会忽略掉顶层const。

    同时,当形参有顶层const时,传给它常量对象或者非常量对象都是可以的

    void fcn(const int i) {}
    // fcn可以读取i,但不能改变i
    void fcn(int i) {}
    // 这是就存在重复定义,因为函数1会 忽略顶层const,导致函数2与函数1可接收的实参类型可能相同

    指针或引用形参与const

    可以使用一个非常量初始化一个底层const对象

    同时一个普通的引用必须用同类型的对象从初始化

    允许常量引用绑定非常量的对象、字面值 、一般表达式

    int i = 42;
    const int *cp = &i;
    // 正确、非常量初始化底层const
    const int &r = i;
    // 正确、常量引用绑定非常量对象
    const int &r2 = 42;
    // 正确、常量引用绑定字面值
    int  *p = cp;
    // 类型不匹配
    int &r3 = r;
    // 类型不匹配
    int &r4 = 42;
    // 不能用字面值初始化一个非常量引用
    void reset(int &i);
    void reset(int *ip);
    
    int i = 0;
    const int ci = i;
    string::size_type  ctr = 0;
    
    reset(i);
    reset(&i);
    // 正确
    
    reset(&ci);
    reset(ci);
    reset(42);
    reset(ctr);
    // 错误、类型不匹配

    应尽量使用常量引用

    1、避免误导实参可修改

    2、避免限制实参的类型

    数组形参

    因为数组存在两个性质:

    1、不允许拷贝数组

    2、使用数组时通常会将其转换成指针

    通过性质1可知,无法以值传递的方式使用数组参数

    通过性质2可知,当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针

    以下三个函数等价。每个函数的形参类型都是const int*

    void print(const int*);
    void print(const int[]);
    void print(const int[10]); 
    // 这里的10表示我们期望数组含有多少元素,实际不一定

    当给print函数传递一个数组时,实参自动地转换成指定数组首元素的指针,数组的大小对函数的调用没有影响。

    以数组作为形参的函数也必须确保使用数组时不会越界

    因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸。

    管理指针形参有三种常用的技术

    1、使用标记指定数组的长度

    void print(const char *cp) {
        if (cp)
            while (*cp)
                cout << *cp++;
    }
    // 处理c风格字符串时会遇到结束标记

    使用标准库规范

    void print(const int *beg, const int *end) {
        while (beg != end)
            cout << *beg++ << endl;
    }
    // 使用标准库
    // begin()返回首元素指针
    // end()返回尾后元素指针

    显式传递一个表示数组大小的形参

    void print(const int ia[], size_t size) {
        for (size_t i = 0; i < size; i++) 
            cout << ia[i] << endl;
    }
    // print(ia, end(ia) - begin(ia));

    只要传递给函数的size值不超过数组实际的大小,函数就是安全的

    数组形参与const

    当函数不需要对数组元素执行写操作时,数组形参应该是指向const的指针,当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针

    数组引用形参

    形参可以是数组的引用。

    void print(int (&arr)[10]) {
        for (auto elem : arr)
            cout << elem << endl;
    }
    // 形参是数组的引用,维度是类型的一部分
    
    f(int &arr[10]);
    // 将arr声明成了引用的数组
    f(int (&arr)[10]);
    // arr是具有10个整数的整型数组的引用

    由于数组的大小是构成数组类型的一部分,这样的引用数组无疑限制了函数的可用性。

    传递多维数组

    多维数组实际上是数组的数组

    将多维数组传递给函数时,真正传递的是指向数组首元素的指针,因为我们处理的是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针,数组第二维的大小都是数组类型的一部分,不鞥省略!

    void print(int (*matrix)[10], int rowSize);
    // matrix指向数组的首元素,该数组的元素由10个整数构成的数组
    // matrix为指向含有10个整数的数组的指针
    
    void print(int matrix[][10], int rowSize);
    // matrix声明一个二维数组,实际上形参指向含有10个整数的数组的指针
    
    int *matrix[10];
    // 10个指针构成的数组
    int (*matrix)[10];
    // 指向含有10个整数的数组的指针

     含有可变形参的函数

    为了编写能处理不同数量实参的函数,C++提供两种主要的方法

    1、如果所有实参的类型相同,可以传递一个名为initializer_list的标准类型

    2、如果实参的类型不同,我们可以编写一种特殊的函数,也就是 可变参数模板。

    initializer_list形参

    如果函数的实参未知但是全部实参的类型都相同,我们可以使用initializer_list类型的形参。用来表示某种特定 类型的值的数组。该类型是一个模板类。大部分操作与vector相同,但是不同之处在于initializer_list对象中的元素永远都是常量值。无法改变对象中元素的值

    void error_meg(initializer_list<string> il) {
        for (auto beg = il.begin(); beg != il.end(); ++beg)
            cout << *beg << " ";
        cout << endl;
    }
    int list_elem(initializer_list<int> il)
    {
        int sum = 0;
        for (auto it = il.begin(); it != il.end(); it++)
            sum += *it;
        return sum;
    }
    int main(int argc, char *argv[])
    {
        cout << list_elem({ 1,2,3,4,5,6 }) << endl;
        return 0;
    }
    // output
    21

    省略符形参

    省略符形参是为了便于C++程序访问某些特殊的C代码而设置的。类似于

    void foo(parm_list, ...);
    void foo(...);

    第一种形式指定了foo函数的部分形参类型,对于这些形参的实参将会执行正常的类型检测,省略符形参所对应的实参无需类型检查

    返回类型和return语句

    return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。

    无返回值函数

    没有返回值的return语句只能用在返回类型是void的函数中,返回void的函数如果没有return语句,则函数的最后一句后会隐式执行return。

    一个返回类型是void的函数也能使用return+表达式语句,不过此时表达式必须是一个返回void的函数。

    void printStr()
    {
        cout << "It's ok!!!" << endl;
    }
    
    void print()
    {
        return printStr();
    }
    int main(int argc, char *argv[])
    {
        print();
        return 0;
    }
    // output 
    It's ok!!!

    有返回值函数

    只要函数的返回类型不是void,则该函数内每条return必须返回一个值。返回值的类型必须与函数的返回类型相同,或者能隐式转换成函数的返回类型

    值是如何被返回的

    返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。

    如果函数返回一个值,返回值被拷贝到调用点、

    如果函数返回一个引用,该引用仅是它所引对象的一个别名。

    不要返回局部变量的指针或引用

    函数完成后,它所占用的存储 空间也随之被释放掉。因此,函数终止意味着函数变量的引用将指向不再有效的区域。

    如果我们想要确保返回值安全,我们需要引用哪些在函数之前就已经存在的对象

    返回类类型的函数和调用运算符

    调用运算符()与点运算符.和箭头运算符->相同。并且符合左结合律。

    引用返回左值 

    函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值,返回其它类型得到右值。

    我们能为返回类型是非常量引用的函数的结果赋值

    char& get_val(string &str, string::size_type ix)
    {
        return str[ix];
    }
    
    int main(int argc, char *argv[])
    {
        string s("a value");
        cout << s << endl;
        get_val(s, 0) = 'A';
        cout << s << endl;
        return 0;
    }
    // Output
    a value
    A value

     返回数组指针

    如果想定义一个返回数组的指针,可以使用类型别名来重写数组名。

    typedef int arrT[10];
    using arrT = int[10];
    arrT* func(int i);
    // func返回一个指向含有10个整数的数组的指针

    声明一个返回数组指针的函数

    如果想在声明func时不使用类型别名,那么就要牢记被定义的名字后面数组的维度

    int arr[10];
    // arr是一个含有10个整数的数组
    int *p1[10];
    // p1是含有10个指针的数组
    int (*p2)[10];
    // p2是一个指针,指向含有10个整数的数组

    返回数组指针的函数形式如下

    Type (*function(parameter_list)) [dimension]

    int (*func(int i))[10];

    func(int i)表示调用func函数时需要一个int类型的实参

    (*func(int i))意味着我们可以对函数调用的结果执行解引用操作

    (*func(int i))[10]表示解引用func的调用将得到一个大小是10的数组。

    int (*func(int i))[10]表示数组中的元素是int类型

    尾置返回类型

    尾置返回类型跟在形参列表后面并以一个->符号开头。为了表示函数真正的返回类型跟在形参列表之后 ,我们在本该出现返回类型的地方放置一个auto

    auto func(int i)->int(*)[10];

    func函数返回一个指针,该指针指向一个含有10个整数的数组

    使用decltype

    如果我们知道函数返回的指针将指向哪个数组,就可以使用decltype关键字声明返回类型。

    例如,下面的函数返回一个指针,该指针根据参数i的不同指向两个已知数组的某一个

    int odd[] = {1,3,5,7,9};
    int even[] = {0,2,4,6,8};
    
    decltype(odd) *arrPtr(int i)
    {
        return (i % 2) ? &odd : &even;
    }

    decltype表示arrPtr返回类型是个指针,并且该指针所指的对象与odd类型一致,因为odd是数组,所以arrPtr返回一个指向含有5个整数的数组的指针。

    decltype并不负责将数组类型转换成对应的指针,所以decltype结果是一个数组,要想表示arrPtr返回指针还必须在函数声明时加一个*符号。

    // 声明一个函数,使其返回数组的引用并且该数组包含10个string对象
    string (&func(string (&arrStr)[10]))[10];
    // 类型别名
    using arrS = string[10];
    arrS& func1(arrS& arr);
    
    // 尾置返回类型
    auto func2(arrS& arr)->string(&)[10];
    
    // decltype
    string arrT[10];
    decltype(arrT)& func3(arrS& arr);
    // 修改arrPtr使其返回数组的引用
    auto arrRef(int i)->int(&)[10];
    // 实践
    int odd[] = { 1,3,5,7,9 };
    int even[] = { 0,2,4,6,8 };
    
    decltype(odd) *arrPtr(int i)
    {
        return (i % 2) ? &odd : &even;
    }
    
    auto arrRef(int i)->int(&)[5]
    {
        return (i % 2) ? odd : even;
    }
    
    int main()
    {
        int (*x)[5] = arrPtr(0);
        int (&y)[5] = arrRef(1);
        for (int i = 0; i != 5; i++)
            cout << (*x)[i] << " ";
        cout << endl;
        for (int i = 0; i != 5; i++)
            cout << y[i] << " ";
        cout << endl;
        return 0;
    }
    0 2 4 6 8
    1 3 5 7 9
    请按任意键继续. . .
  • 相关阅读:
    JavaScript 正则表达式
    JavaScript类型转换
    JavaScript typeof
    JavaScriptBreak 语句 continue 语句
    JavaScript for循环 while循环
    JavaScript 条件语句
    JavaScript 事件
    JavaScript 作用域
    SP2010开发和VS2010专家"食谱"--第四章节—列表定义和内容类型(3)--使用对象模型创建自定义内容类型
    SP2010开发和VS2010专家"食谱"--第四章节—列表定义和内容类型(2)--拓展现有内容类型
  • 原文地址:https://www.cnblogs.com/immjc/p/8076087.html
Copyright © 2020-2023  润新知