zoukankan      html  css  js  c++  java
  • [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
    请按任意键继续. . .
  • 相关阅读:
    28完全背包+扩展欧几里得(包子凑数)
    HDU 3527 SPY
    POJ 3615 Cow Hurdles
    POJ 3620 Avoid The Lakes
    POJ 3036 Honeycomb Walk
    HDU 2352 Verdis Quo
    HDU 2368 Alfredo's Pizza Restaurant
    HDU 2700 Parity
    HDU 3763 CDs
    POJ 3279 Fliptile
  • 原文地址:https://www.cnblogs.com/immjc/p/8076087.html
Copyright © 2011-2022 走看看