C/C++教程

C++学习笔记(四)

本文主要是介绍C++学习笔记(四),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

第6章 - 函数

1. 函数基础

1.1 函数的形参列表

  • 形参列表中的形参通常用都好隔开,即使哥哥形参的类型一样,也必须把两个类型都写出来

    int f3(int v1, v2) {/* ... */}			// 错误
    int f4(int v1, int v2) {/* ... */}		// 正确
    
  • 函数的返回值不能是数组,但可以是指向数组或函数的指针

1.2 局部对象

  • 生命周期 (lifetime)

  • 局部变量(local variable):形参和函数体内部定义的变量

  • 自动对象(automatic object):只存在于快执行期间的对象,形参 是一种自动对象

  • 局部静态对象(local static object):在程序的执行路径第一次经过对象定义语句时初始化,知道程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响

    // 统计自己被调用的次数
    size_t count_calls() {
        static size_t ctr = 0;				// ctr 是局部静态对象,调用结束后,
        return ++ctr;						// ctr 仍然有效
    }
    
    // 输出从1到10(包括)的数字
    int main() {
        for (size_t i = 0; i != 10; ++i)
            cout << count_calls() < endl;
        return 0;
    }
    

1.3 函数声明(函数原型 function prototype)

  • 函数的声明不包括函数体,所以可以省略形参

    void print(vector<int>::const_iterator beg, vector<int>::const_iterator end);
    
  • 函数应该在头文件中声明而在源文件中定义

  • 含有函数声明的头文件应该被包含到定义函数的源文件中

1.4 分离式编译(separate compilation)

2. 参数传递

2.1 传引用形参

  • 通过使用引用形参,函数可以改变实参的值

  • 使用引用以避免拷贝

    // 比较两个 string 对象的长度,由于 string 对象可能会很长,应避免直接拷贝
    bool &isShorter(const string &s1, const string &s2) {
        return s1.size() < s2.size()
    }
    
    • 如果函数无需改变引用形参的值,最好将其声明为常量引用

2.2 const 形参和实参

  • 允许定义若干具有相同名字的函数,前提是不同函数的形参列表有明显区别

    void fcn(const int i) {/* .. */}			// fcn 能读取 i,但不能向 i 写值
    void fcn(int i) {/* ... */}					// 错误:重复定义了 fcn(int)
    
  • 可以使用非常量初始化一个底层 const 对象,但是不能用底层 const 对象来初始化一个非常量

2.3 数组形参

  • 数组的两个性质:

    • 不允许拷贝;
    • 使用数组是会将其转换成指针
  • 数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外信息

    • 使用标记指定数组长度:适用于有明显结束标记且该标记不会与普通数据混淆的情况

    • 使用标准库规范:传递指向数组首元素和尾后元素的指针

      void print(const int *beg, const int *end) {
          while(beg != end)
              cout << *beg++ << endl;
      }
      
      int j[2] = {0, 1};
      print(begin(j), end(j));
      
    • 显式传递一个表述数组大小的形参

      void print(const int ia[], size_t size) {
          for (size_t i = 0; i != size; ++i)
              cout << ia[i] << endl;
      }
      
      int j[] = {0, 1};
      print(j, end(j) - begin(j));
      
  • 数组引用形参

    void print (int (&arr)[10]) {
        for (auto elem : arr)
            cout << elem << endl;
    }
    
    • &arr 两端的括号必不可少

      f(int &arr[10]) 			// arr 是引用的数组
      f(int (&arr)[10])			// arr 是具有10个整数的整型数组的引用
      
    • 数组的大小是过程数组类型的一部分,限制了 print 函数的可用性

  • 多维数组

    void print(int (*matrix)[10], int rowSize) {/* ... */}
    // int (*matrix)[10]; 指向含有10个整数的数组的指针
    
    • 多维数组的首元素本身就是一个数组,指针就是一个指向数组的指针;
    • 多维数组第二位(以及后面所有维度)的大小都是数组类型的一部分,不能省略

2.5 main 处理命令函选项

// 假设 main 函数位于可指向文件 prog 内, 向程序传递下面的选项
prog -d -o ofile data0

int main(int argc, char *argv[]) { ... }

argv[0] = "prog";					// 或者 argv[0] 也可以指向一个空字符串
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;
  • 第二个形参 argv 是一个数组, 它的元素是指向C风格字符串的指针; 第一个形参 argc 表示数组中字符串的数量
  • 使用 argv 中的实参时, 可选的实参从 argv[1] 开始, argv[0] 保存程序的名字, 而非用户输入

2.6 含有可变形参的函数

3. 返回类型和 return 语句

3.1 有返回值的函数

  • 引用返回左值

    char &get_val(string &str, string::size_type ix) {
        return str[ix];
    }
    
    int main() {
        string s("a value");
        cout << s << endl;
        get_val(s, 0) = 'A';			// 将 s[0] 的值改为 A
        cout << s << endl;
        return 0;
    }
    
    • 如果函数返回类型是常量引用, 则不能给调用的结果赋值
  • 函数可以返回花括号包围的值的列表

  • 递归 (recursive function): 函数调用了自身

    // 计算 val 的阶乘
    int factorial(int val) {
        if (val > 1)
            return factorial(val - 1) * val;
        return 1;
    }
    
    • main 函数不能调用自己

3.2 返回数组指针

  • 使用类型别名

    typedef int arrT[10];		// arrT 是一个类型别名, 表示的类型是含有10个整数的数组
    using arrT = int[10];		// 等价于上一条语句
    
    // 声明 func 函数, 该函数返回一个指向含有10个整数的数组的指针
    arrT* func(int i);
    
  • 不使用类型别名

    int arr[10];				// arr 是一个含有10个整数的数组
    int *p1[10];				// p1 是一个含有10个指针的数组, 指针指向整数
    int (*p2)[10];				// p2 是一个指针,指向含有10个整数的数组
    
    // 和上面的声明一样, 定义返回数组指针的函数时, 数组的维度必须跟在函数名字之后, 例如
    int (*func(int i))[10];
    
    • func(int i) 表示调用 func 函数时需要一个 int 类型的实参
    • (*func(int i)) 表示可以对函数调用的结果执行解引用操作
    • (*func(int i))[10] 表示解引用得到的是一个大小是10的数组
    • int (*func(int i))[10] 表示数组中的元素是 int 类型
  • 使用尾置返回类型

    // func 接受一个 int 类型的参数, 返回指向含有10个整数的数组的指针
    auto func(int i) -> int(*) [10]
    
  • 使用 decltype

    int odd[] = {1, 3, 5, 7, 9};
    int even[] = {0, 2, 4, 6, 8};
    
    // 返回一个指针, 指向含有5个整数的数组
    decltype(odd) *arrPtr(int i) {			// 对函数调用的结果解引用得到的是 odd 类型
    										// 的数组
        return (i % 2) ? &odd : &even;		//  返回一个指向数组的指针
    }
    

4. 函数重载

  • 重载函数(overloaded function) 同一作用域内的几个函数名字相同但形参列表不同

  • 不允许两个函数除了返回类型外其他所有的要素都相同

  • 顶层 const 不影响传入函数的对象: 一个拥有顶层 const 的形参无法和另一个没有顶层 const 的形参区分开来

  • 如果函数的形参是某种类型的指针或引用, 则通过区分其指向的对象是否为常量可以实现函数重载, 此时的 const 是底层的

  • const_cast 和重载

    string::size_type reset(const string &s) {
        auto ret = s.size();
        for (decltype(ret) i = 0; i != s.size(); ++i) {
            if (s[i] == '.')
                ret = i;
        }
        return ret;
    }
    
    // 重载函数 shorterString, 返回值不是 从const string &, 而是 string &
    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);
    }
    
  • 如果在内层作用域中声明名字, 它将隐藏外层作用域中声明的同名实体

5. 特殊用途语言特性

5.1 默认实参 default argument

typedef string::size_type sz;
string screen(sz ht = 24, sz = wid = 80, char backgrnd = ' ');
  • 一旦某个形参被赋予了默认值, 它后面的所有形参都必须有默认值
  • 在给定的作用域中一个形参只能被赋予一次默认实参,
  • 通常应该改在函数声明中指定默认实参, 并将该声明放在合适的头文件中
  • 局部变量不能作为默认实参

5.2 内联函数和 constexpr 函数

  • 内联 (inline) 函数可避免函数调用的开销, 在函数的返回类型前面加上关键字 inline 就可以将函数声明成内联函数

    inline const string & 
    shortString(const string &s1, const string &s2) {
        return s1.size() <= s2.size() ? s1 : s2;
    }
    
    • 内联说明知识像编译器发出的一个请求, 编译器可以选择忽略这个请求
    • 一般来说, 内敛机制用于优化规模较小, 流程直接, 调用频繁的函数
  • constexpr 函数: 能用于常量表达式的函数

    • 函数的返回类型及所有形参的类型都得是字面值类型
    • 函数体中必须有且只有一条 return 语句
    • 为了能在编译过程中随时展开, constexpr 函数被隐式地定义为内联函数
    • 允许 constexpr 函数的返回值并非一个常量
  • 调试帮助

    • assert 预处理宏

      assert (expr);				// 当表达式 expr 为假, 输出信息并终止程序的执行
      							// 当表达式 expr 为真, 什么都不做
      
    • NDEBUG 预处理变量

      • 如果定义了NDEBUG , 则 assert 什么都不做, 默认状态下没有定义 NDEBUG

        // 在main.c 文件的一开始写 #define NDEBUG
        $ CC -D NDEBUG mian.C # use /D with the Microsoft compiler
        
      • 如果未定义NDEBUG , 执行 #ifndef#endif 之间的代码; 如果定义了NDEBUG , 这些代码将被忽略掉

    • 调试中可能用到的名字

      名字作用类型定义者
      __func__存放函数名字const char 的一个静态数组编译器
      __FILE__存放文件名字符串字面值预处理器
      __LINE__存放当前行号整型字面值预处理器
      __TIME__存放文件编译时间字符串字面值预处理器
      __DATA__存放文件编译日期字符串字面值预处理器

6. 函数指针

  • 函数指针指向的函数而非对象, 指向某种特定类型, 函数的类型由它的返回类型和形参类型共同决定, 与函数名无关

    // 比较两个 string 对象的长度
    bool lengthCompare(const string &, const string &);
    
    // pf 指向一个函数, 该函数的参数是两个 const string 的引用, 返回值是 bool 类型
    bool (*pf)(const string &, const string &);			// 未初始化
    
    • *pf 代表 pf 是一个指针

    • 后面的形参列表表示 pf 指向的是函数

    • 左侧的 bool 表示函数的返回值是 bool 类型

    • *pf 两侧的括号不能省略, 不写括号时

      // 声明一个名为 pf 的函数, 其返回值类型为 bool*
      bool *pf(const string &, const string &);	
      
  • 使用函数指针

    • 把函数名作为一个值使用时, 该函数自动得转换成指针

      pf = lengthCompare;				// pf 指向名为 lengthCompare 的函数
      pf = &lengthCompare;			// 等价于上一条语句
      
    • 可以直接使用指向函数的指针调用该函数, 无须提前解引用指针

      // 以下三条语句等价
      bool b1 = pf("hello", "goodbye");
      bool b2 = (*pf)("hello", "goodbye");
      bool b3 = lengthCompare("hello", "goodbye");
      
    • typedef

      // Func 和 Func2 等价, 为函数类型
      typedef bool Func(const string &, const string &);
      typedef decltype(lengthCompare) Func2;
      
      // FuncP 和 FuncP2 等价, 为指向函数的指针
      typedef bool (*FuncP)(const string &, const string &);
      typedef decltype(lengthCompare) *FuncP2;
      
    • using

      using F = int(int*, int);				// F 是函数类型, 不是指针
      using PF = int(*)(int *, int);			// PF 是指针类型
      
这篇关于C++学习笔记(四)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!