Posts C++primer-P6 函数
Post
Cancel

C++primer-P6 函数

第六章 函数

函数基础

  • 函数定义:包括返回类型、函数名字和0个或者多个形参(parameter)组成的列表和函数体。
  • 调用运算符:调用运算符的形式是一对圆括号 (),作用于一个表达式,该表达式是函数或者指向函数的指针。
  • 圆括号内是用逗号隔开的实参(argument)列表。
  • 函数调用过程:
    • 1.主调函数(calling function)的执行被中断。
    • 2.被调函数(called function)开始执行。
  • 形参和实参:形参和实参的个数类型必须匹配上。
  • 返回类型void表示函数不返回任何值。函数的返回类型不能是数组类型或者函数类型,但可以是指向数组或者函数的指针。
  • 名字:名字的作用于是程序文本的一部分,名字在其中可见。

局部对象

  • 生命周期:对象的生命周期是程序执行过程中该对象存在的一段时间。
  • 局部变量(local variable):形参和函数体内部定义的变量统称为局部变量。它对函数而言是局部的,对函数外部而言是隐藏的。
  • 自动对象:只存在于块执行期间的对象。当块的执行结束后,它的值就变成未定义的了。
  • 局部静态对象static类型的局部变量,生命周期贯穿函数调用前后。 直到程序终止才被销毁

    1
    2
    3
    4
    5
    6
    
    // 这段程序是有错误的 书上例
        void cont_call(){
          static int count;
          ++ count;
          cout << count << endl;
      }
    

函数声明

  • 函数声明:函数的声明和定义唯一的区别是声明无需函数体,用一个分号替代。函数声明主要用于描述函数的接口,也称函数原型
  • 在头文件中进行函数声明:建议变量在头文件中声明;在源文件中定义。 头文件详解
  • 分离编译CC a.cc b.cc直接编译生成可执行文件;CC -c a.cc b.cc编译生成对象代码a.o b.oCC a.o b.o编译生成可执行文件。

参数传递

  • 形参初始化的机理和变量初始化一样。
  • 引用传递(passed by reference):又称传引用调用(called by reference),指形参是引用类型,引用形参是它对应的实参的别名。
  • 值传递(passed by value):又称传值调用(called by value),指实参的值是通过拷贝传递给形参。

传值参数

  • 当初始化一个非引用类型的变量时,初始值被拷贝给变量。
  • 函数对形参做的所有操作都不会影响实参。 ???????????
  • 指针形参:常用在C中,C++建议使用引用类型的形参代替指针。

    当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。可以通过指针间接访问对象更改对象。

传引用参数

  • 通过使用引用形参,允许函数改变一个或多个实参的值。
  • 引用形参直接关联到绑定的对象,而非对象的副本。
  • 使用引用形参可以用于返回额外的信息
  • 经常用引用形参来避免不必要的复制。
  • void swap(int &v1, int &v2)
  • 如果无需改变引用形参的值,最好将其声明为常量引用。

const形参和实参

  • 形参的顶层const被忽略。void func(const int i);调用时既可以传入const int也可以传入int

  • 我们可以使用非常量初始化一个底层const对象,但是反过来不行。
  • 在函数中,不能改变实参的局部副本
  • 尽量使用常量引用。
  1. const引用问题

    1
    2
    3
    4
    
     const int i = 12;
     const int &j = i;  // 引用必须初始化
     //    j = 43;  错误 对常量的引用不可更改
     //    int &j2 = i;   错误 非常量引用指向常量对象
    
  2. 允许把指针定位常量(常量指针)必须初始化 指针地址不再改变

    • 顶层指针
    1
    2
    
     int errNum = 0;
     int * const p = &errNum;  // p指针指向的位置不再改变
    
    • 底层指针
    1
    2
    
     int errBug = 1;
     const int *p1 = &errBug;   // p1 指针内容不再改变
    
  3. 常量表达式问题 值不会改变,切在编译阶段就能计算得到结果的表达式 系统复杂后很难判断,所以,引出constexpr关键字

    1
    2
    
     const int i = 5;
     constexpr int j = i;
    
  4. decltype 关键字 不知道数据类型 希望编译器从表达式当中推导出类型。 decltype(fx()) sum = x;

  5. 引用与常量引用问题 为什么要用常量引用?对于函数不会修改形参的引用,最好都是用常量引用。这么做的目的主要有两个:1.明确的告诉调用函数者,此方法不会改变参数。2.非常量引用会极大的限制方法所能接受的实参类型const int i = 12, 不能传入 void func(int i).

  6. 顶层const 被忽略掉

    1
    2
    3
    4
    5
    6
    7
    
     // 错误 顶层const 被忽略掉 重复定义不能发生重载
     void func1(int i){
         cout << i << endl;
     }
     void func1(const int i){
         cout << i << endl;
     }
    

数组形参

数组不支持拷贝赋值,是用数组时通常将其转化为指针使用。所以数组没有传递。

  • 当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
  • 要注意数组的实际长度,不能越界。

数组作为形参的引用问题f(int (&arr)[10]);

main处理命令行选项

  • int main(int argc, char *argv[]){...}
  • 第一个形参代表参数的个数;第二个形参是参数C风格字符串数组。

可变形参

initializer_list提供的操作(C++11):

操作解释
initializer_list<T> lst;默认初始化;T类型元素的空列表
initializer_list<T> lst{a,b,c...};lst的元素数量和初始值一样多;lst的元素是对应初始值的副本;列表中的元素是const
lst2(lst)拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素。
lst2 = lst同上
lst.size()列表中的元素数量
lst.begin()返回指向lst中首元素的指针
lst.end()返回指向lst中微元素下一位置的指针

initializer_list使用demo:

1
2
3
4
5
6
7
8
void err_msg(ErrCode e, initializer_list<string> il){
    cout << e.msg << endl;
    for (auto bed = il.begin(); beg != il.end(); ++ beg)
        cout << *beg << " ";
    cout << endl;
}

err_msg(ErrCode(0), {"functionX", "okay});
  • 所有实参类型相同,可以使用 initializer_list的标准库类型。
  • 实参类型不同,可以使用可变参数模板
  • 省略形参符: ...,便于C++访问某些C代码,这些C代码使用了 varargs的C标准功能。

返回类型和return语句

无返回值函数

没有返回值的 return语句只能用在返回类型是 void的函数中,返回 void的函数不要求非得有 return语句。

有返回值函数

  • return语句的返回值的类型必须和函数的返回类型相同,或者能够隐式地转换成函数的返回类型。
  • 值的返回:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
  • 不要返回局部对象的引用或指针
  • 引用返回左值:函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值;其他返回类型得到右值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      char &get_val(string &str,string::size_type ix){
      return str[ix];
      }
    
      int maintest(){
          string s("a value");
          get_val(s,1) = 'a';
          cout  << s << endl;
      }
    
  • 列表初始化返回值:函数可以返回花括号包围的值的列表。(C++11

    1
    2
    3
    
          vector<int> test(){
              return {123}
          }
    
  • 主函数main的返回值:如果结尾没有return,编译器将隐式地插入一条返回0的return语句。返回0代表执行成功。

返回数组指针

数组没有拷贝,要么返回指针,要么返回引用

  • Type (*function (parameter_list))[dimension]
  • 使用类型别名: typedef int arrT[10]; 或者 using arrT = int[10;],然后 arrT* func() {...}
  • 使用 decltypedecltype(odd) *arrPtr(int i) {...}
  • 尾置返回类型: 在形参列表后面以一个->开始:auto func(int i) -> int(*)[10]C++11
1
2
3
4
5
6
7
8
9
10
11
12
  // 1. 数组指针
    int arr[10];  // 十个整数的数组
    int* arr1[10];  // 十个指针的数组
    int (*arr2)[10] = &arr;   // 一个指针,指向十个整数的数组。

    // 2. 尾置返回类型
    auto func(int i) -> int(*) [10];

    //3. decltype
    int a[10] ={};
    int b[10] ={};
    decltype(a)* method();

举例返回数组的引用并且该数组包含10个string对象

1
2
3
    string str[10];
    auto func1() -> string(&) [10];
    decltype(str)& func2();

函数重载(顶层与底层const 可以多看看)

  • 重载:如果同一作用域内几个函数名字相同但形参列表不同,我们称之为重载(overload)函数。
  • main函数不能重载。
  • 重载和const形参
    • 一个有顶层const的形参和没有它的函数无法区分。 Record lookup(Phone* const)Record lookup(Phone*)无法区分。
    • 相反,是否有某个底层const形参可以区分。 Record lookup(Account*)Record lookup(const Account*)可以区分。
  • 重载和作用域:若在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体,在不同的作用域中无法重载函数名。
  1. 顶层const无法重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 顶层const无法重载
void method639(demo demo){
}
void method639(const demo demo){
}

void method639(demo* demo){
}
void method639(demo* const demo){
}
// 关于顶层const问题 顶层const 指针内地址不可变
void method639(demo* const demo){
    class demo d2;
//    demo = &d2;  错误
}
  1. 底层const 与 引用 是可以区分重载的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void method639(demo& d){
    cout << " 非常量函数 " << endl;
}

void method639(const demo& d){
    cout << " 常量函数 " << endl;
}
void method6391(demo* demo){
    cout << "指针 非常量函数" << endl;
}
void method6391(const demo* demo){

    cout << "指针 常量函数" << endl;
}
  1. const_cast 重载
1
2
3
4
5
6
7
8
const string& shorterString(const string &s1,const string &s2){
    return s1.size() <= s2.size() ? s1:s2;
}

string &shorterString(string &s1,string &s2){
    auto &r = shorterString(const_cast<const string&>(s1),const_cast<const string&>(s2));
    return const_cast<string&>(r);
}

特殊用途语言特性

默认实参

  • string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');
  • 一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。

内联(inline)函数

  • 普通函数的缺点:调用函数比求解等价表达式要慢得多。
  • inline函数可以避免函数调用的开销,可以让编译器在编译时内联地展开该函数。
  • inline函数应该在头文件中定义。

constexpr函数

  • 指能用于常量表达式的函数。
  • constexpr int new_sz() {return 42;}
  • 函数的返回类型及所有形参类型都要是字面值类型。
  • constexpr函数应该在头文件中定义。

constexpr函数 这个简单说一下: 为什么要用常量表达式? 效率 仅此而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 *  constexpr修饰的函数,简单的来说,如果其传入的参数可以在编译时期计算出来,
 那么这个函数就会产生编译时期的值。
 *  但是,传入的参数如果不能在编译时期计算出来,那么constexpr修饰的函数就和普通函数一样了。
 *  不过,我们不必因此而写两个版本,所以如果函数体适用于constexpr函数的条件,
 *  可以尽量加上constexpr。而检测constexpr函数是否产生编译时期值的方法很简单,
 *  就是利用std::array需要编译期常值才能编译通过的小技巧。这样的话,
 即可检测你所写的函数是否真的产生编译期常值了。
 */
 
 using namespace std;

constexpr int foo(int i)
{
    return i + 5;
}

int main()
{
    int i = 10;
    std::array<int, foo(5)> arr; // OK
    
    foo(i); // Call is Ok
    
    // But...
    std::array<int, foo(i)> arr1; // Error  数组的初始化要用常量表达式 
   
}

调试帮助

  • assert预处理宏(preprocessor macro):assert(expr);

开关调试状态: u CC -D NDEBUG main.c可以定义这个变量NDEBUG

1
2
3
4
5
void print(){
    #ifndef NDEBUG
        cerr << __func__ << "..." << endl;
    #endif
}

函数匹配

  • 重载函数匹配的三个步骤:1.候选函数;2.可行函数;3.寻找最佳匹配。
  • 候选函数:选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。
  • 可行函数:考察本次调用提供的实参,选出可以被这组实参调用的函数,新选出的函数称为可行函数(viable function)。
  • 寻找最佳匹配:基本思想:实参类型和形参类型越接近,它们匹配地越好。

函数指针

  • 函数指针:是指向函数的指针。
  • bool (*pf)(const string &, const string &); 注:两端的括号不可少。
  • 函数指针形参
    • 形参中使用函数定义或者函数指针定义效果一样。
    • 使用类型别名或者decltype
  • 返回指向函数的指针:1.类型别名;2.尾置返回类型。

p222 二刷没看懂

  1. 函数指针实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
     bool lengthCompare(const string &,const string &);
     bool (*pf)(const string &,const string &);
     bool lengthCompare(const string &s1,const string &s2){
         return true;
     }
    
     void  test604(){
         pf = lengthCompare;
         bool b = pf("hey","heye");
         cout << b << endl;
     }
    
  2. 函数重载

    1
    2
    3
    4
    
     void ff(int*);
     void ff(unsigned int);
    
     void (*pf1)(unsigned int) = ff;
    
This post is licensed under CC BY 4.0 by the author.

Contents

Trending Tags