[Essential C++ notes] Chapter 2 面向过程的编程风格

原书:Essential C++, Stanley B. Lippman, 电子工业出版社, 2013.
章节:第2章 面向过程的编程风格
环境:CLion + MinGW


2.1 如何编写函数
  1. 函数的四个组成部分:返回类型、函数名、参数列表、函数体

  2. 函数声明与函数定义

    (1) 函数声明必须指明返回类型、函数名、参数列表,但不必提供函数体,即所谓的函数原型。函数声明让编译器得以检查后续出现的使用方式是否正确——是否有足够的参数、参数类型是否正确等

    (2) 函数定义包括函数原型和函数体

  3. 返回Fibonacci数列第n个元素的函数

    #include <iostream>
    #include <cstdlib>		// 书中说使用exit函数应包含cstdlib头文件,但在当前环境下不包含也可使用
    using namespace std;
    bool fibonacci_elem(int index, int* elem);	// 声明fibonacci_elem函数
    int main() {
        int index;
        cin >> index;
        cout << fibonacci_elem(index) << endl;
        return 0;
    }
    
    int fibonacci_elem(int index){
        if(index <= 0){		// 用户输入不合理索引时,调用exit传入一个值,其为程序结束时的状态值
            cerr << "Your should enter a postive integer." << endl;
            exit(-1);
        }
        int elem = 1;
        int n1 = 1, n2 = 1;
        for(int i = 3; i <= index; ++i){
            elem = n1 + n2;
            n1 = n2;
            n2 = elem;
        }
        return elem;
    }
    

    (1) 使用exit函数处理用户的不合理输入,如上述代码所示

    (2) 抛出异常(exception),表示函数收到了错误的索引值

    (3) 返回0,其不在Fibonacci序列中,提示用户异常,但此方式不好

    (4) 改变fibonacci_elem函数返回值,使其能表示能否计算出用户想要的值

    1) 刚开始我是这样写的,使用指针改变变量值

    #include <iostream>
    using namespace std;
    bool fibonacci_elem(int index, int* elem);	// 声明fibonacci_elem函数
    // bool fibonacci_elem(int, int*);			// 函数声明可以省略参数名称,只指明参数类型
    int main() {
        int index, elem = 0;
        cin >> index;
        if(fibonacci_elem(index, &elem))
            cout << elem << endl;
        return 0;
    }
    
    bool fibonacci_elem(int index, int* elem){	
        const int max_index = 45;				// 限制最大索引值,否则结果可能会溢出
        if(index <= 0 || index > max_index){
            cerr << "Your should enter a positive integer no more than " 
                 << max_index << endl;
            return false;	// 返回值为布尔类型,表示能否计算得到指定索引的Fibonacci序列元素
        }
        int ans = 1;
        int n1 = 1, n2 = 1;
        for(int i = 3; i <= index; ++i){
            ans = n1 + n2;
            n1 = n2;
            n2 = ans;
        }
        *elem = ans;		// 通过指针返回对应的Fibonacci序列元素值
        return true;
    }
    

    2) 书中是这样写的,使用引用改变变量值

    #include <iostream>
    using namespace std;
    bool fibonacci_elem(int, int &);	// 函数声明可以省略参数名称,只指明参数类型
    int main() {
        int index, elem = 0;
        cin >> index;
        if(fibonacci_elem(index, elem))
            cout << elem << endl;
        return 0;
    }
    
    bool fibonacci_elem(int index, int &elem){
        const int max_index = 45;		// 限制最大索引值,否则结果可能会溢出
        if(index <= 0 || index > max_index){
            cerr << "Your should enter a positive integer no more than "
                 << max_index << endl;
            return false;	// 返回值为布尔类型,表示能否计算得到指定索引的Fibonacci序列元素
        }
        int ans = 1;
        int n1 = 1, n2 = 1;
        for(int i = 3; i <= index; ++i){
            ans = n1 + n2;
            n1 = n2;
            n2 = ans;
        }
        elem = ans;			// 通过引用将变量设为对应的Fibonacci序列元素值
        return true;
    }
    
  4. 打印Fibonacci数列的前n个元素的函数

    #include <iostream>
    #include <iomanip>
    using namespace std;
    bool print_fibonacci(int);
    int main() {
        int index;
        cin >> index;
        print_fibonacci(index);
        return 0;
    }
    
    bool print_fibonacci(int index) {
        const int max_index = 45;
        if(index <= 0 || index > max_index){
            cerr << "Your should enter a positive integer no more than "
                 << max_index << endl;
            return false;
        }
        if(index >= 1)
            cout << setw(11) << setfill(' ') << left << 1;	// 固定长度为11,左对齐
        if(index >= 2)
            cout << setw(11) << setfill(' ') << left << 1;
        int n1 = 1, n2 = 1, elem;
        for(int i = 3; i <= index; i++) {
            elem = n1 + n2;
            n1 = n2;
            n2 = elem;
            cout << setw(11) << setfill(' ') << left << elem << (i % 5 == 0 ? "\n" : "");
            // 固定长度为11,左对齐,每行打印5个数
        }
        cout << endl;
        return true;
    }
    
2.2 调用函数
  1. 交换两个变量的值的函数

    • 按值传递pass by value:函数无法达到交换的目的
    void swap(int val1, int val2) {   // 错误写法,不能改变原val1和val2的值
        int tmp = val1;
        val1 = val2;
        val2 = tmp;
    }
    int main() {
        int a = 0, b = 1;
        swap(a, b);
        cout << a << " " << b << endl;	// 输出"0 1",并未交换a和b的值
        return 0;
    }
    

    (1) 传给swap()的对象被复制了一份,原对象和副本之间没有任何关联,即swap函数中的val1, val2分别和main函数中的a, b,仅仅是存储相同的值,并非同一个对象

    (2) 当我们调用函数时,会在内存中建立程序堆栈(program stack),提供每个函数参数以及函数定义的每个对象(统称为局部对象)的存储空间。函数完成后,这些内存就被释放掉,或者说从程序堆栈中被pop出来

    • 传址pass by reference
    void swap(int &val1, int &val2) {   // 参数声明为引用类型
        int tmp = val1;
        val1 = val2;
        val2 = tmp;
    }
    int main() {
        int a = 0, b = 1;
        swap(a, b);						// 调用函数语句无需改变
        cout << a << " " << b << endl;	// 输出"1 0",交换a和b的值
        return 0;
    }
    
  2. 对一个vecotr内的整数值排序(冒泡排序)的函数

    其中swap函数和bubble_sort函数均是用了pass by reference的方式传递参数

    #include <iostream>
    #include <vector>
    using namespace std;
    void display(vector<int> &vec);
    void swap(int &val1, int &val2);		// 冒泡排序子程序传递分别绑定俩变量的引用给交换子程序
    void bubble_sort(vector<int> &vec);		// 主函数传递绑定vec的引用给冒泡排序子程序
    int main() {
        int a[8] = {8, 34, 3, 13, 1, 21, 5, 2};
        vector<int> vec(a, a+8);
        cout << "vector before sort: ";
        display(vec);
        cout << "vector after sort: ";
        bubble_sort(vec);
        display(vec);
        return 0;
    }
    void bubble_sort(vector<int> &vec) {	// 冒泡排序, 时间复杂度O(n^2)
        for(int i = 0; i < vec.size(); i++)    
            // 每次迭代vec[i]到vec[vec.size()-1]中最小的元素冒泡至vec[i]
            for(int j = i + 1; j < vec.size(); j++)
                if(vec[j] < vec[i])
                    swap(vec[i], vec[j]);
    }
    void swap(int &val1, int &val2) {   
        // pass by reference, 参数必须为引用类型,若为int类型,则不能改变原对象的值
        int tmp = val1;
        val1 = val2;
        val2 = tmp;
    }
    void display(vector<int> &vec) {
        for(int i = 0; i < vec.size(); i++)
            cout << vec[i] << ' ';
        cout << endl;
    }
    

    程序执行结果

    vector before sort: 8 34 3 13 1 21 5 2
    vector after sort: 1 2 3 5 8 13 21 34
    
  3. pass by reference

    (1) 面对引用的所有操作,都相当于面对"引用绑定的对象"的操作

    int i = 1024, &r = i, *p = &i;
    int j = 4096;
    r = j;		// 将j的值赋给r绑定的对象,即i的值变为4096
    p = &r;		// 将r绑定对象的地址赋给p,即p指向r绑定对象,而非让p指向r
    // 事实上,引用并非对象,故指针无法指向引用
    

    (2) 例外:decltype函数参数为引用时,将返回引用的类型,而非引用所指对象的类型

    int a = 0, &b = a;
    decltype(b) c = a;	// b为int &型,decltype返回该类型,故c为int &型,绑定变量a
    

    (3) pass by reference的好处

    1) 使函数能够直接对所传入的对象进行修改

    2) 降低复制大型对象的额外负担

    如bubble_sort函数可以声明如下,传入vec对象的拷贝,并把排序后的vector对象返回给主函数,也能达到同样的目的。但相对于第一种声明方式,复制vector对象使用了额外的存储空间以及复制vector对象的时间

    // void bubble_sort(vector<int> &vec);
    vector<int> bubble_sort(vector<int> vec);	// 占用额外的存储空间
    

    又如在display函数中,可以声明如下,传入vec对象的拷贝,但缺点如上所述

    // void display(vector<int> &vec);
    void display(vector<int> vec);
    

    (4) const + pass by reference

    进一步地,若使用pass by reference时,函数不会改变所传递对象,则可以声明为底层const

    因为我们不会在display中改变vector对象,所以可以声明如下,该常量引用参数将指向main函数中的非常量对象(这不会引起问题,但不能让非常量引用绑定常量对象)

    void display(const vector<int> &vec) {
        for(int i = 0; i < vec.size(); i++)
            cout << vec[i] << ' ';
        cout << endl;
    }
    
  4. 以指针方式传递实现传址

    #include <iostream>
    #include <vector>
    using namespace std;
    void display(const vector<int> *);
    void swap(int *, int *);
    void bubble_sort(vector<int> *);
    int main() {
        int a[8] = {8, 34, 3, 13, 1, 21, 5, 2};
        vector<int> vec(a, a+8);
        cout << "vector before sort: ";
        display(&vec);
        cout << "vector after sort: ";
        bubble_sort(&vec);
        display(&vec);
        return 0;
    }
    void bubble_sort(vector<int> *vec) {
        for(int i = 0; i < (*vec).size(); i++)       
        // 每次迭代vec[i]到vec[vec.size()-1]中最小的元素冒泡至vec[i]
            for(int j = i + 1; j < (*vec).size(); j++)
                if((*vec)[j] < (*vec)[i])
                    swap(&(*vec)[i], &(*vec)[j]);	
        			// 将vec指针所指vector对象的第i和j个元素的地址传递给swap函数
    }
    void swap(int *val1, int *val2) {   
        int tmp = *val1;
        *val1 = *val2;
        *val2 = tmp;
    }
    void display(const vector<int> *vec) {
        for(int i = 0; i < (*vec).size(); i++) {
            cout << (*vec)[i] << ' ';
        }
        cout << endl;
    }
    
  5. 作用域及范围

    (1) 除了static例外,函数内定义的对象只存在于函数执行期间,函数执行完后会从程序堆栈中pop()以释放内存空间

    (2) 不应返回函数内定义的局部对象的地址(引用或指针),否则程序可能会对不存在的对象进行寻址操作,正确的方式是按值传递,返回局部对象的副本,它在函数之外依然存在

    (3) 作用域

    1) 对象在程序内的存活区域称为对象的作用域scope

    2) 在函数内定义的局部对象具有local scope,在函数外声明的对象具有file scope

    1. 内置类型的对象,若在file scope内定义则被默认初始化为0(指针默认初始化为空指针,bool类型默认初始化为false,引用不是对象),若在local scope内定义则不会进行默认初始化
  6. 动态内存管理

    (1) local scope和file scope均由系统自动管理,另一种存储期形式为动态范围dynamic extent,其内存由程序的空闲空间分配而来,也成为堆内存heap memory,其内存由程序员自行管理,通过new表达式分配,通过delete表达式释放

    (2) new表达式

    int *pi = new int(100);		// new Type
    int *pi = new int(100);		// new Type(initial_value)		
    

    new Type由heap分配一个类型为Type的对象,返回该对象的地址,默认情况下heap分配的对象皆未初始化

    int *p = new int;			// heap分配的对象不初始化,即使在file scope也一样
    int a, *pa = &a;			// a未初始化,但在file scope中定义,故将默认初始化为0
    int main() {
        cout << *p << endl;		// p所指int对象未初始化,将输出奇怪的值
        cout << *pa << endl;	// pa所指int对象默认初始化为0,将输出0
    }
    

    程序输出结果

    38673552
    0
    

    new Type(initial_value)由heap分配一个类型为Type的对象,初始化为initial_value,并返回其地址

    (3) delete表达式

    heap分配的对象具有动态范围,可以持续存活,知道delete表达式将其释放

    int *pi = new int(1024);
    delete pi;
    

    (4) 数组操作

    int *pia = new int[24];		
    // 从heap中分配一个数组,拥有24个整数,均未初始化,返回第一个数组元素地址
    delete [] pia;
    // 删除数组中的所有对象,在delete和数组指针之间加上[]
    

    (5) 内存泄漏memory leak

    若程序员不适用delete表达式释放heap分配的对象,则其永远不会被释放,应该防止memory leak的发生

2.3 提供默认参数值
  1. 一般地,以参数传递作为函数间的沟通方式,优于直接将对象定义于file scope,否则我们必须了解函数和file scope中对象的工作逻辑,程序也可能难以在其他环境重用

  2. 在冒泡程序中将跟踪信息打印到ofil文件

    void bubble_sort(vector<int> &vec, ofstream *ofil = nullptr);
    void bubble_sort(vector<int> &vec, ofstream *ofil) {	// 冒泡排序, 时间复杂度O(n^2)
        for(int i = 0; i < vec.size(); i++)
            // 每次迭代vec[i]到vec[vec.size()-1]中最小的元素冒泡至vec[i]
            for(int j = i + 1; j < vec.size(); j++)
                if(vec[j] < vec[i]){
                    if(ofil)		// 若ofil不为nullptr,则将交换信息输出至ofil对象
                        (*ofil) << "about to call swap! i: " << i << " j: " << j
                             << "\tswapping: " << vec[i] << " with " << vec[j] << endl;
                    swap(vec[i], vec[j], ofil);
                }
    }
    int main(){
        // ...
        bubble_sort(vec);			// ofil默认为nullptr,程序不打印交换信息
        ofstream ofil("data.txt");
        bubble_sort(vec, &ofil);	// 传入ofil,程序将交换信息输出至ofil对象,即data.txt
    }
    
  3. 默认参数值规则

    (1) 默认值的解析是从最右边进行的,若为某个参数提供了默认值,则这一参数右侧的所有参数也必须具有默认参数值

    void fun1(int a, int b = 0, int c);		// 非法,b及其右侧的所有参数都得具有默认参数值
    void fun2(int a, int b = 0, int c = 1); // 合法
    

    (2) 默认值只能指定一次,可以在函数声明处或函数定义处,但不能两个地方都指定。可把默认值指定放在函数声明处,函数声明放在头文件中,程序通过包含对应头文件的方式调用该函数,从而提高函数的可见性(visiblity)

2.4 使用局部静态对象
  1. 局部对象在函数执行完成后会被释放,而为了节省函数间通信问题而在file scope中定义对象不是一种合适的做法

  2. 静态局部对象local static object

    (1) 局部静态对象在函数内定义,用关键字static标识,该函数执行完成后不会立刻释放该对象的内存空间,并在不同的函数调用过程中持续存在

    const vector<int> * fibon_seq(int size){
        static vector<int> elems;
        // ... 
        return &elems;
    }
    

    (2) 打印Fibonacci数列前n个数,使用局部静态对象作为存储Fibonacci序列的vector对象

    #include <iostream>
    #include <vector>
    using namespace std;
    vector<int>* fibonnaci_seq(int size);
    void display(vector<int>*, int);
    int main(){
        int size;
        while(cin >> size)
            display(fibonnaci_seq(size-1), size);
        return 0;
    }
    void display(vector<int>* p, int size){		// 输出Fibonacci数列的前size个元素
        if(p){
            for(int i = 0; i < size; i++)
                cout << (*p)[i] << ' ';
            cout << endl;
        }
    }
    vector<int>* fibonnaci_seq(int size){
        const int max_size = 45;
        if(size < 0 || size >= max_size){
            cerr << "invalid size" << endl;
            return nullptr;
        }
        static vector<int> elem;	// 定义为空的vector,利用push_back()将数值放在vector末端
        for(int i = elem.size(); i <= size; i++){	// 若elem已保存相应元素,则无需重复计算
            if(i <= 1)
                elem.push_back(1);					// push_back()将数值放在vector末端
            else
                elem.push_back(elem[i-1] + elem[i-2]);
        }
        return &elem;
    }
    
2.5 声明inline函数
  1. 重构fibon_elem()函数,将各个小功能分解为独立的函数

    inline bool is_size_ok(int size) {		// 函数声明为inline
        const int max_size = 45;
        if(size < 0 || size > max_size){
            cerr << "invalid size" << endl;
            return false;
        }
        return true;
    }
    int fibo_seq(int size) {
        if(!is_size_ok(size))
            return 0;
        // ... 
    }
    
  2. 函数声明为inline,表示要求编译器在每个函数调用点上,将函数的内容展开,通过减少函数调用可能获得性能改善

  3. 函数指定为inline,只是对编译器提出了要求,而编译器是否执行该请求,视编译器而定

  4. 一般将体积小、常被调用且并不复杂的函数声明为inline

  5. 编译器必须在inline函数被调用时将其展开,故必须保证此时inline函数的定义是有效的,因此inline函数一般定义在头文件中

2.6 提供重载函数
  1. 对于同一个函数(名),传入不同类型甚至不同数量的参数,利用了函数重载机制function overloading

  2. 参数列表不相同(类型或个数不相同均可)的多个函数,可以拥有相同的函数名称。编译器根据调用时传入的实际参数,决定调用哪一个函数

  3. 编译器能根据参数列表区分函数名相同的两个函数,但无法通过返回类型区分

    ostream& display_message(char ch);
    bool display_message(char ch);	// 错误,不能靠返回类型区分同名函数
    display_message('a');			// 编译器无法确定调用哪个函数
    
  4. display_message函数

    #include <iostream>
    using namespace std;
    void display_message();
    void display_message(int);
    void display_message(string);
    void display_message(int, string);
    int main(){
        int a = 1;
        string s = "Hello world";
        display_message();
        display_message(a);
        display_message(s);
        display_message(a, s);
        return 0;
    }
    void display_message(){
        cout << "an example of function overloadling" << endl;
    }
    void display_message(int msg){
        cout << msg << endl;
    }
    void display_message(string msg){
        cout << msg << endl;
    }
    void display_message(int msg1, string msg2){
        cout << msg1 << " " << msg2 << endl;
    }
    
2.7 定义并使用模板函数
  • 如下所示,多个display_message()函数处理不同的参数,但每个函数体都颇为相似,我们可以使用函数模板function template机制
void display_message(const string&, const vector<int>&);
void display_message(const string&, const vector<double>&);
void display_message(const string&, const vector<string>&);
void display_message(const string &msg, const vector<int> &vec){	// 三者的函数体一致
    cout << msg;
    for(int i = 0; i < vec.size(); i++)
        cout << vec[i] << ' ';
}
// ... 
  1. function template将参数列表中指定的部分或全部参数的类型信息抽取出来

  2. function template以template开头,紧接着以<>包围一或多个标识符,用以表示我们希望推迟决定的数据类型,用于利用这一模板产生函数时必须提供类型信息

    template <typename elemType> 	// typename关键字表示elemType是一个暂缓决定类型的占位符
    void display_message(const string &msg, const vector<elemType> &vec){
    	cout << msg;
        for(int i = 0; i < vec.size(); ++i) {
            elemType t = vec[i];
            cout << t << ' ';
        }
    }
    int main(){
        string arr[] = {"Hello", "world"};
        vector<string> vec(arr, arr + 2);
        display_message("", vec);	// 直接调用,编译器会将elemType绑定为string类型
        return 0;
    }
    

    typename关键字表示elemType在display_message()函数中是一个暂时放置类型的占位符,elemType是个任意的名称

  3. function template的参数通常由两种类型构成,一是明确的类型(如上例中的msg),另一类是暂缓决定的类型(上例中的vec)

  4. 模板函数中由多个暂缓决定的类型

    template <typename msgType, typename elemType>	// msgType和elemType为暂缓决定的类型
    void display_message(const msgType &msg, const vector<elemType> &vec){
        cout << msg << endl;
        for(int i = 0; i < vec.size(); ++i){
            elemType t = vec[i];
            cout << t << ' ';
        }
    }
    
  5. 重载模板函数的选择

    (1) 一般来说,若函数具有多种实现方式,可以将其重载

    (2) 若希望程序代码的主体不变,只改变其中用到的数据类型,则通过function template达到目的

  6. function template同时也是重载函数

    template <typename elemType>	// 第一个display_message函数的第二个参数为vector类型
    void display_message(const string &msg, const vector<elemType> &vec);
    
    template <typename elemType>	// 第二个display_message函数的第二个参数为list类型
    void display_message(const string &msg, const list<elemType> &li)
    
2.8 函数指针

吐槽:本节标题为Pointers to Functions Add Flexibility,可加上also much complexity

  1. 通过vector返回六种数列,需要六个函数

    const vector<int> *fibon_seq(int size);
    const vector<int> *lucas_seq(int size);
    const vector<int> *pell_seq(int size);
    const vector<int> *triang_seq(int size);
    const vector<int> *square_seq(int size);
    const vector<int> *pent_seq(int size);
    
    const vector<int> *fibon_seq(int size) {
    	const int max_size = 45;
    	static vector<int> elems;
    	if(size <= 0 || size > max_size) {
    		cerr << "invalid size" << endl;
    		return 0;
    	}
    	for(int i = elems.size(); i < size; i++) {
    		if(i <= 1)
    			elems.push_back(1);
    		else
    			elems.push_back(elems[i-1] + elems[i-2]);
    	}
    	return &elems;
    }
    // 其余五个数列对应的函数
    
    // 判断能否返回Fibonacci序列对应索引的元素,还需另外定义lucas_elem等五个函数?
    bool fibo_elem(int pos, int &elem) {	
        const vector<int> *pseq = fibo_seq(pos);	// 唯一和数列相关的部分,调用fibo_seq函数
        if(!pseq){
            elem = 0;
            return false;
        }
        elem = (*pseq)[pos-1];
        return true;
    }
    // 能否消除与fibo_seq的关联,从而用一个函数seq_elem实现对所有六个数列的判断?
    
  2. 函数指针

    函数指针pointer to function必须指明所指函数的返回类型和参数列表

    // 指向一类函数的指针,函数必须以一个int为参数,返回类型必须为指向const vector<int>的指针
    const vector<int>* (*seq_ptr) (int); 
    // seq_ptr可指向上述的fibon_seq等六个函数
    

    定义函数指针后,编写seq_elem使其能服务于六个数列

    bool seq_elem(int pos, int &elem, const vector<int>* (*seq_ptr) (int)) {	
        const vector<int> *pseq = seq_ptr(pos);	// 调用seq_ptr指向的函数
        if(!pseq){
            elem = 0;
            return false;
        }
        elem = (*pseq)[pos-1];
        return true;
    }
    
  3. 函数的地址:提供函数的名称即可

    seq_ptr = fibo_seq;		// 将fibo_seq()函数的地址赋给函数指针seq_ptr
    

    为了使seq_ptr方便地指向不同的数列函数,可以定义一个存放函数地址的数组

    const vector<int>* (*seq_array[])(int) = {
    	fibo_seq, lucas_seq, pell_seq,
    	triang_seq, square_seq, pent_seq
    };
    

    借助枚举类型存放的常量对上述数组进行索引操作

    enum ns_type{
    	ns_fibo, ns_lucas, ns_pell,
    	ns_triang, ns_square, ns_pent	// 各项称为枚举项,其值依次为0, 1, 2, 3, 4, 5
    };
    

    调用时,可使用对应的枚举项作为数组索引值

    seq_ptr = seq_array[ns_fibo];
    
2.9 设定头文件
  1. 为了避免在多个文件中进行重复的函数声明,可以把函数声明放在头文件中,并在每个程序代码文件内包含这些函数声明,这也减少了维护函数声明的工作量

  2. 把与数列处理有关的函数的声明都放在一个头文件内,扩展名一般为.h

    // NumSeq.h
    bool seq_num(int pos, int &elem);
    const vector<int> *fibon_seq(int size);
    const vector<int> *lucas_seq(int size);
    const vector<int> *pell_seq(int size);
    const vector<int> *triang_seq(int size);
    const vector<int> *square_seq(int size);
    const vector<int> *pent_seq(int size);
    
  3. 函数声明放在头文件,但一般不把函数定义放在头文件,因为函数定义只有一份,若多个程序文件引用同一个头文件则可能造成重复定义

    例外:inline函数应该放在头文件内,因为在每个inline函数的调用点上,编译器必须取得函数定义以将其展开

  4. file scope定义的对象,若可能被多个文件访问,也应该被声明于头文件中,声明时需要使用extern关键字

    const int seq_cnt = 6;
    // const对象不需要加extern,因为const object无法跨文件访问,重新定义即可
    extern const vector<int>* (*seq_array[seq_cnt]) (int);
    // 声明seq_array对象
    
  5. 尖括号与双引号的区别

    #include <iostream>
    #include "NumSeq.h"
    

    若头文件被认为是标准的或项目专属的头文件,用尖括号括住,编译器将从某些默认的磁盘目录中开始寻找文件;若是用户提供的头文件,则用双引号括住,编译器将从包含此文件的程序所在的磁盘目录中开

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值