Essential C++ Notes

第二章 面向过程的编程风格

引用(reference)

什么是引用?

引用是一个对象的别名或者说是handle, 对引用进行操作,等同于对所引用的对象进行操作

如何声明一个引用?

1
2
3
int val = 1024;
int &rval = val; // rval 是一个 reference, 代表一个int对象
int *pval = &val; // pval 是一个pointer, 指向一个int对象

引用与指针有何区别?

  • reference 必定引用一个对象, 而pointer可能并不指向一个对象
  • 函数参数形式不同
1
2
3
void test(int& ref){} // reference
void test(int* ptr){} // pointer
// 两者本质上都是传入指针

传参时,何时用reference/pointer?

函数传参有两种类型,传引用传值

传值时, 编译器会将实参的值复制一份传给形参, 而传引用可以降低实参的复制开销(只复制指针)

  • 希望对传入的参数(实参)进行修改
  • 降低复制大型对象的额外负担
1
void test(const int& ref){};// const 告诉别人使用传引用并非是想修改实参,而是不想复制实参

static

全局对象与静态对象区别?

1
2
static int glb = 2; // 静态变量
int glb = 2; // 全局变量

两者作用域不同, 生存期相同

  • 静态变量作用域是file scope
  • 全局变量作用域是整个源程序

静态对象与局部静态对象有何区别?

1
2
3
4
static int glb = 2; // 静态变量
void test(){
static int loc_glb = 2; // 局部静态变量
}
  • 两者生存期相同

  • 作用域不同, 静态变量作用域为file scope, 局部静态变量作用域在function scope

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void count(){
    static int loc_glb; // 值被该函数所共享
    for(int i=0;i<10;++i){
    cout<<++loc_glb<<endl;
    }
    }
    // out
    1
    2
    ...
    10

Inline

inline 使用方法需要注意哪些地方?

  • inline 只是对编译器提出的一种要求, 编译器是否执行, 需要视编译器而定

  • inline 函数定义常常放在头文件中

    这是为什么了?

    在每个调用该inline函数的文件中包含该头文件。这种方法保证了每个inline函数只有一个定义,且程序员无需复制代码,并且不可能在程序的生命周期中引起无意的不匹配的事情。

    ​ ——摘自《C++ Primer》(第三版)

    如果在头文件中定义非inline函数,一旦该头文件被多个文件包含,就会造成该非inline函数的“重定义”,因而,不建议将非inline函数的定义放在头文件中,但是非inline函数的声明是可以放在头文件中的。

inline 函数与普通函数有何区别?

  • 编译器在遇到inline function 会拿到该函数的定义, 将这个函数内容展开,即将该函数的调用操作, 改为以一分函数定义替换(不会发生函数调用)

    为什么需要引入inline function?

    • 函数调用是需要开销的(编译器需要在栈中开辟内存, 保存当前的程序上下文,并进入该函数上下文中,执行结束后, 弹栈, 继续执行下一行代码…)
    • 当存在一些代码行数不多,功能简单, 需要频繁调用的函数时, 其函数调用开销比较大, 这时可以将这些函数展开, 以提高效率

何时需要用到inline?

  • inline function可以省去函数调用的开销,从而提高函数的执行效率,但是如果函数体内代码的执行时间相比于函数调用时间长的多的话,inline函数也就没有什么优势了。
  • 因此对于一些代码行数不多,功能简单, 需要频繁调用的函数, 可以考虑inline

inline函数定义常放在头文件里面, 为什么不会出现重定义问题?

重载(overload)

何为重载?

通过重载机制 ,可以定义一系列 函数参数不同(参数类型不同或者参数个数不同), 具有相同函数名称的函数

为何要有重载机制?

为了避免程序中出现过多不必要的函数名字(比如 display_int(), display_str())

通过重载机制, 要实现的目标是: 统一具有相同功能,只是参数不同函数名

编译器是如何区别这些函数名称相同的函数呢?

当我们想调用一个函数时, 必须要指定函数名称函数参数, 通过传入不同的函数参数, 可以确定要调用的函数.

所以如果函数返回类型不同是无法区分每个重载函数的

模板(template)

template 与 overload 目的是近乎相同的, template是为了提高函数的复用率, overload是为了提高函数名称的复用率

template 有什么用?

template 实际上是复用数据类型, 对于函数功能相同, 只是参数数据类型不同, 可以用template

函数指针(Pointers to Functions)

Pointers to Functions 有啥用?

本质是为了复用函数, 对于函数参数相同, 函数名称不同的函数, 可以通过Pointers to Functions来复用

如何声明一个Pointers to Functions?

如果我们要自己设计一个指向函数的指针, 当我们要利用这个指针进行函数调用时

  • 其一, 我们需要知道这个函数的参数 (特殊之处!!!)

    不同于传统指针, 为了要知道所执行函数的参数情况, 需要在Pointers to Functions初始化时指出

  • 其二, 我们需要知道这个函数的返回数据类型

1
2
3
4
5
6
7
// 如下所示, 如果*PointerToFunction 不加括号, 则会理解成 
// 返回类型为 const vector<int>**
// 指针名称为PointerToFunction, 这是错的
// const vector<int>* *PointerToFunction(int)
const vector<int>* (*PointerToFunction)(int)
// const vector<int>* 指明函数的返回类型
// (int) 指明函数的参数

如何使用Pointers to Functions?

正确的声明类似于 const vector<int>* (*PointerToFunction)(int)

正确的使用类似于 const vector<int>* ret = PointerToFunction(5)

1
2
3
4
5
6
7
8
9
10
11
// 获得指定类型序列,在指定位置pos上的值, 成功获得返回true否则返回false
bool seq_elem(int pos, int &elem, const vector<int>* (*seq_ptr)(int)){
// 调用seq_ptr所指向的函数, 函数作用是生成pos个指定类型序列
const vector<int>* pseq = seg_ptr(pos);
if (!pseq){
elem = 0;
return false;
}
elem = (*pseq)[pos-1];
return true;
}

如何获得某个函数的地址?

函数名称即为函数地址

1
2
const vector<int>* fab_seq(int);
const vector<int>* (*seq_ptr)(int) = fab_seq; // 将函数地址赋值给seq_ptr

如何定义一个数组, 里面元素都是函数指针?

1
2
3
4
5
6
7
8
9
10
11
12
13
const vector<int>* func1(int);
const vector<int>* func2(int);
// const vector<int>* 告诉编译器数组中函数指针所指向的函数的返回类型
// (*)表示数组中元素是函数指针
// (int) 表示函数参数
const vector<int>* (*arr[])(int) = {fun1, fun2};
for(const vector<int>* (*elem)(int): arr){
elem(5); // 执行函数
}
// 如何更快的通过函数指针数组,调用指定函数
// 可以通过enum实现, 给每个函数指针在数组的索引定义名称, 可以通过执行名称,调用函数
enum fun_idx {ns_nature, ns_fib};
arr[ns_nature](5);

设定头文件

头文件内容类似于向外暴露出来的API接口, 因此头文件一般存放以下内容

  • 函数声明

    可以放函数定义不?

    不可以, 对于普通函数(inline function除外), 如果将函数定义放在头文件中, 当有多处引用到该头文件时,会出现函数重复定义错误

  • const 和 static 变量,可以放在头文件中

    const对象默认是static的,而不是extern的,所以即使放在头文件中声明和定义。多个cpp引用同一个头文件,互相也没有感知,所以不会导致重复定义。

  • 模板的定义(包括非inline函数/成员函数)要求声明和实现都必须放在头文件

  • 类(class)的定义,可以放在头文件中

    用类创建对象的时候,编译器要知道对象如何布局才能分配内存,因此类的定义需要在头文件中。

    • 一般情况下,我们把类内成员函数的定义放在cpp文件中,但是如果直接在class中完成函数声明+定义的话,这种函数会被编译器当作inline的,因此满足上面inline函数可以放在头文件的规则。
    • 但是如果声明和定义分开实现,但是都放在头文件中,那就会报重复定义了!!

# include 何时使用””何时使用<>?

编译器如果碰到""会在当前源程序目录下查找所要引用文件, 如果碰到<>则会从某些默认磁盘目录下查找

  • 用户自定义(与被引用文件在同一目录下)用""
  • 系统库等非用户实现文件用<>

第三章 泛型编程风格

泛型

泛型如何实现?

借助template实现数据类型泛型, 利用pointer可以实现对象泛型

再具体点, 可以思考下如何才能实现泛型?

STL总共有两种组件构成, 容器(container)包括vector,map, list和泛型算法(generic algorithm)包括find, sort, replace

我觉得实现泛型的基础是实现基本操作的泛型,如: 遍历, 插入, 删除

而对于不同的数据结构,遍历,插入,删除方式不同,

  • 因此可以针对不同数据结构分别实现其基本的操作, 并将其封装成class

  • 为了实现数据类型泛型,采用template

  • 为了实现数据结构泛型, 采用pointer

    为什么要用pointer?

    如果要遍历vector和array, 做好的方法是使用一个pointer指向所要遍历元素, 使用pointer自增来进行遍历

    1
    2
    3
    4
    5
    template <typename T>
    void traverse(const T* array, int size);
    void traverse(const T* array, T* sentinel); // 使用哨兵
    void traverse(const T* begin, const T* end); // 直接指定begin和end, 泛型程度最高, 无需begin执行的是何种数据结构, 这里的begin和end就是STL实现的Iterator(泛型指针)
    // 通过泛型指针可以更好的使用泛型
  • 由此STL实现了Iterator(泛型指针)

第四章 基于对象的编程风格

构造函数(Constructors)

在class object 定义好后, 编译器会自动根据获得的参数,选择合适的构造函数, 进行初始化

构造函数与普通函数有何区别?

  • 无返回类型, 无返回值(构造函数的作用就是初始化,无需返回任何东西)
  • 函数名与类名相同

如何进行class object 的初始化?

class scope 运算符: ::

  • 利用构造函数传参进行初始化
1
2
3
4
5
6
7
8
9
10
11
12
// Cocofe.h
#include <iostream>

class Cocofe {
public:
Cocofe(){_value=1;show();}; // A
Cocofe(int val){_value=val;show();} // B
Cocofe(int x, int y){_value=x * y;show();} // C
void show(){std::cout<<_value<<std::endl;}
private:
int _value;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "Cocofe.h"
using namespace std;

int main(int argc, char **args) {
Cocofe cocofe; // 调用A构造函数
Cocofe cocofe1(5); // 调用B构造函数
Cocofe cocofe2 = 7; // 调用B构造函数, 这个构造函数的使用也比较特殊点
Cocofe cocofe3(5,6); // 调用C构造函数
Cocofe cocofe4(); // 需要注意的是, 这行代码不会调用A构造函数, 为了兼容c, cocofe4()被视作函数
return 0;
}
// out
1
5
7
30
  • 利用Constructor定义的第二种初始化方法, 即所谓的member initialization list(成员初始化列表)
1
2
3
4
5
6
7
8
9
#include <iostream>
class Cocofe {
public:
Cocofe():_next(1),_name("cocofe"){_value=1;}; // 在构造函数后面利用":"来实现初始化类的成员变量
private:
int _value;
int _next;
std::string _name;
};

析构函数(Destructor)

析构函数主要是用来释放在构造函数中或在对象声明周期中被分配的资源

析构函数与一般函数有何区别?

  • 不可被重载
  • 无返回值,无返回类型,无形参
  • 函数名为”~”+类名
  • 无需手动调用,由编译器自动调用

拷贝构造函数(Copy Constructor)

通过拷贝另一个class object 来进行class object的初始化

若不定义拷贝构造函数, 而利用另一个class object 来进行class object的初始化会产生什么问题?

若不定义拷贝构造函数,则默认的行为是进行成员的逐一初始化, 即将B对象的成员逐一复制到A对象中

1
2
3
4
5
6
7
8
9
10
11
12
class Matrix{
public:
Matrix(int row, int col):_row(row), _col(col){
_pmat = new double[_row*_col];
}
~Matrix(){
delete[] _pmat;
}
private:
int _row, _col;
double* _pmat;
}
1
2
Matrix mat(5,6);  
Matrix mat2 = mat; // A

上面A行代码中,将mat的_row, _col, _pmat 逐一复制到mat2的相同成员变量中,

  • _row 和 _col 不会出现问题, 因为他们复制的是值, 即使mat对象被垃圾回收后, _row和 _col 不会受影响
  • mat2 对象中 _pmat 和 mat 对象中的 _pmat 指向同一个内存空间(复制的是指针, 指向相同对象), 当mat被回收后, mat 的 _pmat所指向的内存也被回收, 因此 mat2 对象中 _pmat所指向的内存空间是非法的, 会出现问题

为了避免这种问题, C++ 引入的拷贝构造函数

拷贝构造函数如何定义?

可以根据构造函数的参数判断是否为拷贝构造函数, 拷贝构造函数的形参是对象的引用(const reference)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Matrix{
public:
Matrix(int row, int col):_row(row), _col(col){
_pmat = new double[_row*_col];
}
// 定义拷贝构造函数
Matrix(const &mat):_row(mat._row),_col(mat._col){
_pmat = new double[_col*_row];
// 复制值
for(int i=0; i< _col*_row; ++i){
_pmat[i] = mat._pamt[i];
}
}
~Matrix(){
delete[] _pmat;
}
private:
int _row, _col;
double* _pmat;
}

可变(mutable) 与 不变(const)

这边的变与不变是针对于member function 是否会修改 class object的内容而言的,

先看下代码示例

1
2
3
4
5
6
7
8
9
10
11
int sum(const Triangular &trian){
if(! trian.length()){
return 0;
}
int val, sum = 0;
trian.next_reset(); // 将trian._next设为-1
while(trian.next(val)){ // _next递增遍历, 相加
sum += val;
}
return sum;
}

在上面代码中有个问题, const Triangular &trian指明 trian object是个const object, 所以编译器要保证sum函数内的代码不可改变trian object成员的值, 因此若要sum正常执行, 则需要保证trainmember function(trian.length(), trian.next_reset(), trian.next(val)) 不改变trian object的成员值

编译器是如何判断某个类对象的成员函数是否会改变类的成员的值?

需要在类成员函数的声明定义中用const进行标识, 这类成员函数称为const member function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Triangular{
public:
// const member function
int length() const{return _length;}
int beg_pos() const{return _beg_pos;}
int elem(int pos) const; // 取得指定位置值
// non-const member function
bool next(int& val);
bool next_reset(){_next = _beg_pos - 1;}
...
private:
int _next;
int _beg_pos;
int _length;
static vector<int> _elems;
}
1
2
3
4
5
6
7
8
9
10
Triangular::elem(int pos) const{
return _elems[pos];
}
Triangular::next(int& val){
if(_next < _beg_pos + _length - 1){
val = _elems[_next++]; // 改变_next值, class object 对象被改变
return true;
}
return false;
}

实际上要确保sum函数正常运行, 则需要将trian.length(), trian.next_reset(), trian.next(val) 设成 const member function, 但是要进行遍历的话, _next 难免是需要修改的,就不能设成const, 因此, 到此还是无法正常运行sum函数, 这时就需要引入mutable

什么是mutable?

mutable用来修饰 data member, 被修饰的数据成员称为 可变数据成员(Mutable Data member)

当我们修改_next时, 对象中的vector本质上是不会变得, _next只是起到指针的作用, 因此可以这么理解, 修改 _next, 其所在的对象仍然是const, 而与 _next类似的这类数据成员可以用mutable修饰

1
2
3
4
...
privite:
mutable int _next;
...

因此, 将_next改为mutable时, trian.next_reset(), trian.next(val)可以当做const member function

this指针

this指针类似于Python 中的self

类中的每个成员函数,可以理解为默认的第一个参数都是this, 其指向调用该方法的对象, 但是C++把它隐藏了

1
2
3
4
5
6
Triangular copy(const Triangular &rhs){
_next = rhs._beg_pos - 1; // 类似于 this->_next = rhs._beg_pos - 1;
_length = rhs._lenght;
_beg_pos= rhs._beg_pos;
return *this;
}

静态成员(static member function and static data member)

静态成员作用域在类中, 与普通成员的区别有:

  • 静态成员不绑定于任一类对象, 因此要使用这些静态成员需要以类名::成员方式进行调用

    1
    vector<int> Triangular::elems;
  • 静态成员函数中不可包括非静态成员

覆盖/重载操作符(Operator Override/OverLoad)

当我们实现一个类时, 类间的一些操作需要我们自己实现, 比如复数类的四则运算, 类与类间的拷贝等操作

如何重写/重载操作符?

通过operator+操作符来表明所要重写的操作符

如何重写递增运算符的前缀(++*)和后缀(*++)版本

两者都是单目运算符, 应该都不需要传入参数, 为了区分两者, 规定后缀(\*++)版本有个int参数

1
2
3
4
// 后缀版递增
Coord operator++(int);
// 前缀版递增
Coord operator++();
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// Coord.h
//
// Created by 游海群 on 2018/5/1.
//

#ifndef KDXF_COORD_H
#define KDXF_COORD_H


#include <iostream>

class Coord {
public:
Coord(int x, int y){
_x = x;
_y = y;
}
// // 默认的*操作符实现代码
// Coord operator*(){
// return *this;
// }
Coord operator*(const Coord& coord){
return {this->_x*coord.x_point(), this->_y*coord.y_point()};

}
Coord operator+(const Coord& coord){
return {this->_x+coord.x_point(), this->_y+coord.y_point()};
}

// 后缀版递增
Coord operator++(int){
auto tmp = *this;
_x += 1;
_y += 1;
return tmp;
}
Coord operator++(){
_x += 1;
_y += 1;
return *this;
}

int x_point() const{
return _x;
};
int y_point() const{
return _y;
};
void x_set(int x){
_x = x;
}
void y_set(int y){
_y = y;
}
private:
int _x;
int _y;
};
#endif //KDXF_COORD_H
1
2
3
4
5
6
7
8
9
10
// IostreamReconstruction.h
//
// Created by 游海群 on 2018/5/1.
//
#ifndef ESSENTIALCNOTES_SHOW_H
#define ESSENTIALCNOTES_SHOW_H
#include "Coord.h"
std::ostream& operator<<(std::ostream &os, const Coord& coord);
std::istream& operator>>(std::istream &is, Coord& coord);
#endif //ESSENTIALCNOTES_SHOW_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// IostreamReconstruction.cpp
//
// Created by 游海群 on 2018/5/1.
// 输入输出流重载
//
#include "IostreamReconstruction.h"
std::ostream& operator<<(std::ostream &os, const Coord& coord){
os<<"("<<coord.x_point()<<","<<coord.y_point()<<")";
return os;
}
std::istream& operator>>(std::istream &is, Coord& coord){
// 输入形式为(x,y)
char ch1,ch2,ch3;
int x,y;
is>>ch1>>x>>ch2>>y>>ch3;
coord.x_set(x);
coord.y_set(y);
return is;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "EssentialC/Coord.h"
#include "EssentialC/IostreamReconstruction.h"
int main(int argc, char **args) {
Coord coord1(3,5);
Coord coord2(4,6);
auto * coord3 = new Coord(5,7);
// ostream 运算符重载
std::cout<<coord1<<coord2<<std::endl; // print (3,5)(4,6)
// 乘法运算符重载
std::cout<<coord1*coord2<<std::endl; // print (12,30)
// 加法运算符重载
std::cout<<coord1+coord2<<std::endl; // print (7,11)
// 指针取值运算符重载
std::cout<<*coord3<<std::endl; // print (5,7)
// istream 运算符重载
std::cin>>coord2; // input (999,999)
std::cout<<coord2<<std::endl; // print (999,999)
// 后缀版递增重写
std::cout<<coord2++<<std::endl; // print (999,999)
std::cout<<coord2<<std::endl; // print (1000, 1000)
// 前缀版递增重写
std::cout<<++coord2<<std::endl; // print(1001,1001)
}

实现一个function object

何为function object?

类对象class object变得可调用callable, 用于函数式编程

如何实现function object?

类中实现()call 运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// EssentialC/LessThan.h
//
// Created by 游海群 on 2018/4/28.
//

#ifndef KDXF_LESSTHAN_H
#define KDXF_LESSTHAN_H


#include "Coord.h"

class LessThan {
public:
// 实现 call 运算符
bool operator()(const Coord& c1, const Coord& c2){
return c1.x_point() < c2.x_point() && c1.y_point() < c2.y_point();
}
};


#endif //KDXF_LESSTHAN_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "EssentialC/Coord.h"
#include "EssentialC/IostreamReconstruction.h"
#include "EssentialC/LessThan.h"
#include <algorithm>
#include <vector>
int main(int argc, char **args) {
Coord coord1(3,5);
Coord coord2(4,6);
auto * coord3 = new Coord(5,7);
std::vector<Coord> arr{coord2,coord1,*coord3}; // (4,6) (3,5) (5,7)
LessThan ls;
// sort 会调用ls(coord1, coord2), 并根据返回结果进行排序
std::sort(arr.begin(), arr.end(), ls); // (3,5) (4,6) (5,7)
for(Coord& cd : arr){
std::cout<<cd<<" ";
}
std::cout<<std::endl;

}

第五章 面向对象编程

面向对象编程的最主要两个特性是继承inheritance多态polymorphism

何为动态绑定dynamic binding?

1
2
mat.check_in()  // mat is an reference
mat->check_in() // mat is a pointer

动态绑定是指在运行期间, 编译器根据mat所指向的实际对象来决定调用哪个check_in()方法, 即找出实际被调用的究竟是哪一个派生类的check_in函数, 这一解析操作会延迟到运行时(run-time)才进行, 因此称为dynamic binding

编译器在编译时就依据mat所属的类决定调用哪一个check_in()函数, 这称为静态绑定(static binding)

  • 只有使用pointerreference时,才会发生动态绑定
  • 静态绑定是根据pointerreference所属类型, 判断
  • 动态绑定是根据pointerreference所指向的对象类型, 判断
  • 正常情况下,pointerreference属于基类, 指向派生类对象

虚函数/纯虚函数

virtual 有何作用?

virtual —> 告知编译器dynamic binding —> 实现多态

默认情况下member function 的解析(resolution) 是在编译时静态的进行, 若要令其在运行时动态解析, 需要在它的声明前加上关键字virtual

什么是纯虚函数?

不存在定义的虚函数叫做纯虚函数

1
2
3
4
5
6
7
8
9
10
11
class Animal {
public:
Animal():_name("animal"){};
virtual void who(){
std::cout<<_name<<std::endl;
}
// 定义纯虚函数
virtual void say() = 0;
protected:
std::string _name;
};

纯虚函数有什么特性?

1
2
3
4
5
6
7
8
9
10
11
12
#include "Animal.h"

class Dog: public Animal {
public:
Dog():_name("dog"){};
void who(){
std::cout<<_name<<std::endl;
}
// 未实现父类Animal中的纯虚函数say, Dog类也是抽象类,不可被实例化
protected:
std::string _name;
};
  • 定义有纯虚函数的类为抽象类, 不可被实例化, 其子类若也未实现父类的纯虚函数, 也视为抽象类
  • 派生类若实现父类的纯虚函数, 则派生类中的该函数视为虚函数 —> 动态绑定, 具有多态特性(纯虚函数的目的就是为多态而生的)

运行时状态类型鉴定 (Run-Time Type Identification, RTTI)

typeid 可以在运行时, 获取所指向对象的实际类型

1
2
3
Dog dog;
Animal& animal = dog;
cout<<typeid(animal).name()<<" "<< typeid(animal).hash_code()<<endl;

dynamic_cast 和 static_cast

1
2
3
4
if (typeid(*ps) == typeid(Fibonacci)){
Fibonacci *pf = static_cast<Fibonacci*>(ps); // 无条件转换
pf->gen_elems(64);
}
1
2
3
4
// 有条件转换, 如果os所指对象不属于Fibonacci类, 会返回0
if(Fibonacci* pf = dynamic_cast<Fibonacci*>(ps)){
pf->gen_elems(64);
}

dynamic_case 也是一个RTTI运算符,它会进行运行时检测操作

踩坑小记

不要返回一个函数局部引用/指针

1
2
3
4
5
6
7
8
Coord operator*(const Coord& coord){
Coord tmp(this->_x*coord.x_point(), this->_y*coord.y_point());
Coord& ref_coord = tmp; // 这个引用是局部变量!!!
// 返回一个值编译器会做如下动作:
// 复制所返回的值
// 退出该函数, 将该函数所在的栈空间弹出 --> 这会导致这函数所有的局部变量失效
return ref_coord;
}

分析下报错原因

基于上面分析,

  • 如果函数返回一个值, 那么不会出现问题, 因为编译器会在弹出该函数栈空间时,拷贝一份返回值, 并返回
  • 如果函数返回一个指针/引用(引用实际上类似于一个指针, 指向一个对象,但也有不同之处), 当函数的栈空间被弹出时, 所返回的指针/引用所指向的对象, 如果该对象是函数局部对象, 这就会造成其所指向对象是不合法的(已被销毁)

避免造成重复定义

  • 在头文件***.h中定义函数声明
  • ***.cpp中实现函数
  • 项目中只引用头文件

不在头文件中使用using namespace std

为了避免由于头文件被多个文件引用造成命名冲突问题

善用auto类型

auto在C11前表示的是具有自动存储期的局部变量,由于使用人数较少, 在C11中作为一个新的类型指示符

  • 基于 类型推导, 编译器能通过右边表达式算出左边的数据类型
  • 减少人为指定数据类型造成的错误情况
  • 类型推导泛型编程中具有很大作用(其返回类型不确定)
1
2
3
4
5
int a = 100;
float b = 200;
auto c = a + b;
auto z; // 会报错, 使用auto 必须初始化
std::cout<< typeid(c).name()<<std::endl; // print f , 表明c为float类型

参考链接

注意头文件规则,避免链接错误:重复定义(multiple defination)

static作用(修饰函数、局部变量、全局变量)

本文标题:Essential C++ Notes

文章作者:定。

发布时间:2018年4月26日 - 00时04分

本文字数:15,582字

原始链接:http://cocofe.cn/2018/04/26/essential-c-notes/

许可协议: Attribution-NonCommercial 4.0

转载请保留以上信息。