人文社科专题征文work技术向

重读经典《C++ Primer》

2015-08-28  本文已影响1638人  Andrew_liu

简书终于更新了代码高亮功能, 似乎呼唤着我回归, 那么就来一发良心之作
时隔一年, 重读C++ Primer这本圣经, 怀念去年这时基友们一起debug, 一起吃饭, 一起睡觉, 一起分享知识的那个夏天, 以这篇文章纪念我的好朋友, 希望有机会我们再聚在一起吃酒撸串夜灯下诉过去与未来, 同时以我微薄的知识向C++之父敬礼.

本文不会罗列C++基础语法, 只说明需要注意的地方, 所有需要注意的地方均为作者主观观点.

如有任何错误之处, 欢迎斧正.

编程风格

我认为学习一种语言, 一定要学习一种权威的编程风格指南, 就如python中的PEP8.

使用约定的风格, 可以减少协同工作的障碍, 建立程序猿之间的代码友谊. 虽然朋友说要对别人的代码宽容, 而我认为这就像纵容别人犯罪一样, 遵守一定的代码风格, 能瞬间拉近程序猿之间的距离, 增加代码可读性何乐而不为呢? 我喜欢看开源代码的原因之一就是很多著名的开源项目优雅的编程风格

简要的罗列一下建议遵守的编程风格(谷歌风格):

更多细节参考Google 开源项目风格指南

基础

编译与执行:

  1. 预处理阶段: 根据字符#开头的命令, 修改原始C程序
  2. 编译阶段: 将文本文件翻译成汇编程序
  3. 汇编阶段: 汇编器将编译程序翻译成机器语言指令(机器可识别), 并打包成可重定位目标程序
  4. 链接阶段: 将调用函数目标文件合并到程序中, 形成可执行目标文件
//使用GUN编译器g++, -o选项将输入写入文件, 即用来存放可执行文件
$ g++ chapter1.cc -o chapter

变量:

变量的作用我认为有以下几点:

左值与右值:

const关键字:

  1. 通过指定const变量为extern, 可以在整个程序中访问const对象
  2. const引用是指向const对象的引用, 对象可读不可写
  3. 指向const对象的指针, 定义时不需要初始化, 可以对指针重新赋值(修改其中保存的内存地址, 指向其他对象), 但所指向对象中的值不能修改(内存中保存的值不能修改)
  4. const指针, const指针的值(保存的内存地址的值不能修改)不能修改, 也就是不能使const指针指向其他对象
  5. 指向const对象的const指针, 既不能修改指针所指向的对象值(内存地址中保存的值), 也不能修改指针的指向(指针中保存的内存地址)
// file1.cc
extern const int buf_size = 10;
// file2.cc
extern const int buf_size; //使用file1.cc中的buf_size

//指向const对象的指针
const double *cptr;
//const指针
int err_numb = 2;
int *const cur_err = &err_numb; 

预处理器:

常用格式:

#ifndef _XXX_H_
#define _XXX_H_

...

#endif /* _XXX_H_ */

指针:

自增/自减操作符

sizeof操作符

注意sizeof是操作符, 用于获得类型的长度

switch语句执行匹配的case标号相关联的语句后, 会跨越case边界继续执行其他语句, 直到switch结束或者遇到break.

复制传参(pass by value)和引用/指针传参(pass by reference):

static对象:

类内staitc数据成员不属于某个对象(类内声明, 类外定义). 类内static函数没有this指针(用于处理staitc数据成员)

static成员必须在class定义式之外被定义(除非他们是const并且是整型)

class Test {
public:
    Test(const int p): price(p) { std::cout << "constructor Test." << std::endl; }
    void setRate(const double r) { this->rate = r; }
    double getRate() { return rate; };
private:
    static double rate;  //declare
    int price;
};
double Test::rate = 0.53;  //define

int main(int argc, char *argv[]) {
    Test t(10);
    Test t1(20);
    std::cout << t.getRate() << std::endl;  
    std::cout << t1.getRate() << std::endl;
    t.setRate(2.22);
    std::cout << t.getRate() << std::endl;
}

一旦创建static对象被创建, 在程序结束前不会被销毁. 常用于生命周期跨越多个函数调用的对象

inline函数:

重载函数:

  1. 确认候选函数(C++名字查找发生在类型检查之前)
  2. 检查形参个数和形参类型匹配问题

函数指针:

重点理解: 函数指针是指向函数的指针
直接使用函数名等效于在函数名上取地址操作符

// pf是一个指针, *表明了pf的指针身份, pf的类型为bool (const string &, const string &)
bool (*pf)(const string &, const string &);
//类比与普通变量指针, *表示ps是指针, ps的类型为const string
const string *ps;
// typedef简化函数指针定义 cmpFun等价于bool * (const string &, const string &)
typedef bool (*cmpFun)(const string &, const string &);
cmpFun pf;
//typedef简化普通变量指针 pstr等价于 const string *
typedef const string *p_str;
pstr ps;

Stack和Heap

Stack是存在于某作用域的一块内存空间
Heap是由操作系统提供的一块全局内存空间(可动态分配获得此类空间)

namespace

标准库中所有文件被包裹在std命名空间中.

命名空间可以是不连续的

namespace myname {
    ...
}  // 不以分号结束

异常

异常通过throw抛出对象引发的.异常可以传递给给非引用形参任意类型的对象

  1. 通过栈展开(stack unwinding), 沿嵌套函数调用链继续向上, 直至为异常找到一个用于处理异常的catch语句
  2. 捕获所有异常的catch子句形式为(...)
  3. exception类型所定义的唯一操作是what虚函数

类定义了一个新的类型和新的作用域, 切记类定义以分号结束

structclass的唯一差别在于默认访问级别上, struct的成员默认为public, class成员默认为private

理解初始化列表, 初始化列表初始化数据成员(注意const对象或引用类型只能初始化不能赋值), 没有初始化列表的的构造函数在函数体中对数据成员赋值. 成员被初始化的顺序就是定义成员的顺序

// 链式编程
Screen & Screen::move(char c) {
    contents[cursor] = c;
    return *this;  // this是一个指针, 解引用后是一个类类型Screen
}

构造函数和析构函数的顺序

直接上代码会比较清晰

class Foo {
public:
    Foo() { std::cout << "Foo default constructor." << std::endl; }
    Foo(const Foo &foo) { std::cout << "Foo copy constructor." << std::endl; }
    ~Foo() { std::cout << "Foo deconstructor." << std::endl;}
};

class Bar {
public:
    Bar() { std::cout << "Bar default constructor." << std::endl; }
    Bar(const Bar &bar) { std::cout << "Bar copy constructor." << std::endl; }
    ~Bar() { std::cout << "Bar deconstructor." << std::endl;}
};

class Yes {
public:
    Yes() { std::cout << "Yes default constructor." << std::endl; }
    ~Yes() { std::cout << "Yes deconstructor." << std::endl; }
};

class Base {
public:
    Base() { std::cout << "Base constructor." << std::endl; }
    ~Base() { std::cout << "Base deconstructor." << std::endl; }
private:
    Foo foo_;
};

class Derived : public Base {
public: 
    Derived() { std::cout << "Derived constructor." << std::endl; }
    Derived(const Bar &bar, const Yes &yes);
    Derived(const Yes &yes, const Bar &bar);
    ~Derived() { std::cout << "Derived deconstructor." << std::endl;}
private:
    Bar bar_;
    Yes yes_;
};

Derived::Derived(const Bar &bar, const Yes &yes) {
    std::cout << "Derived argument: (bar, yes) constructor." << std::endl;
}

Derived::Derived(const Yes &yes, const Bar &bar) {
    std::cout << "Derived argument (yes, bar) constructor." << std::endl;
}

int main(int argc, char *argv[]) {
    std::cout << "create simple obejct foo and bar." << std::endl;
    Foo foo;
    Bar bar;
    Yes yes;
    std::cout << "create Base class " << std::endl;
    Base base;
    std::cout << "case 1 : (default constructor) " << std::endl;
    Derived derived1;
    std::cout << "case 2 : (argument bar, yes)" << std::endl;
    Derived derived2(bar, yes);
    std::cout << "case 3 : (argument yes, bar)" << std::endl;
    Derived derived3(yes, bar);
}

运行结果:

// 创建三个简单的对象
create simple obejct foo and bar.
Foo default constructor.
Bar default constructor.
Yes default constructor.

// 创建基类对象
create Base class
Foo default constructor.  //先对成员变量初始化
Base constructor. // 调用基类构造函数

// Derived调用默认构造函数
case 1 : (default constructor)
Foo default constructor.  //先调用基类构造函数
Base constructor.
Bar default constructor.  //自身成员变量初始化
Yes default constructor.
Derived constructor.  //调用默认构造函数

// 调用以bar, yes为参数的构造函数
case 2 : (argument bar, yes)
Foo default constructor.
Base constructor.
Bar default constructor.  //注意bar, yes构造的顺序
Yes default constructor.
Derived argument: (bar, yes) constructor.

//调用以yes, bar为参数的构造函数
case 3 : (argument yes, bar)
Foo default constructor.
Base constructor.
Bar default constructor.  //注意bar, yes构造的顺序
Yes default constructor.
Derived argument (yes, bar) constructor.

//析构过程(构造过程的逆)
Derived deconstructor.  // case3的析构
Yes deconstructor.
Bar deconstructor.
Base deconstructor.
Foo deconstructor.

Derived deconstructor.  // case2的析构
Yes deconstructor.
Bar deconstructor.
Base deconstructor.
Foo deconstructor.

Derived deconstructor.  // case1的析构
Yes deconstructor.
Bar deconstructor.
Base deconstructor.
Foo deconstructor.

// Base的析构
Base deconstructor.
Foo deconstructor.

//三个简单对象的析构
Yes deconstructor.
Bar deconstructor.
Foo deconstructor.

参考浅出C++对象模型——理解构造函数、析构函数执行顺序

复制控制

具有指针成员的类一般需要定义自己的复制控制(防止浅拷贝), 复制构造或赋值操作符应该显式使用基类的复制构造或赋值操作符

拷贝赋值函数(赋值操作符重载): 

1. 检测值是否自我赋值(self assignment, 地址比较 this == &object)
2. 左值内存delete清空
3. 分配与右值相同大小内存
4. 赋值到左值

操作符重载原则

对象的创建

C++提供两种方法分配和释放未构造的原始内存:

  1. allocator类, 提供可感知类型的内存分配
  2. 标准库中的operator new和operator delete分配和释放需要的大小的原始的、未类型化的内存
1. ClassName object(para);

// 使用new(先分配内存malloc, 指针类型转换 然后调用构造函数)后, 需要delete(先调用析构函数, 再释放内存free)掉分配的堆内存, 防止内存泄漏
// 该表达式调用名为operator new的标准库函数分配足够大的原始的未类型化的内存
2. ClassName *object = new ClassName(param);

// 当使用delete表示删除动态分配内存时, 首先对object指向的对象析构, 然后调用operator delete的标准库函数释放该对象所用的内存, operator delete不会调用析构函数
3. delete object;

//placement new不分配内存, 而是使用已分配但未构造内存的初始化一个对象(接受一个指针)
4. new (place_address) type  // place_address为指针

关于C++内存分配的new, operator new, placement new

类模版

泛型编程是以独立于任何特定类型的方式编写代码

模板形参可以是类型形参, 也可以是非类型形参

模板本身并不是一种类型, 当编译器看到模板定义的时候, 不立即产生代码, 只有在看到用到模板时, 编译器才会对模板进行实例化

template <class Type> 
class Queue {
public:
    Queue() {}; // 可以使用Queue<Type>, 由编译器推断
private:
    QueueItem<Type> *head;  // 非类作用域空间必须显式使用模板形参
    void destory();
};

template <class Type> 
void Queue<Type>::destory() {
    // something
}

STL

输出/输出流:

顺序容器

顺序容器:将单一类型元素聚集起来成为容器, 然后根据位置来存储和访问这些元素. 顺序容器包括vector, list, deque, 顺序容器的适配器stack(基于deque), queue(基于deque), priority_queue(基于vector)

vector对象动态增长:

vector的元素连续存储. 其中size()函数统计vector已有元素个数, capacity()指在vector必须重新分配存储空间之前可存储的元素个数.可见vector分配存储空间的策略是增幅小于1时取1, 之后每达到2的次方时倍增(变为原来容量的2倍)

// 测试程序
void TestIncrease(std::vector<std::string> &vec) {
    std::cout << "size: " << vec.size() << std::endl;
    std::cout << "capacity: " << vec.capacity() << std::endl;
}

void TestVector() {
    std::vector<std::string> vec;
    TestIncrease(vec);
    for(std::vector<std::string>::size_type ix = 0;
        ix != 24; ++ix) {
        vec.push_back("hello");
        TestIncrease(vec);
    }
}
// 测试结果, 只保留重要部分
size: 0
capacity: 0
size: 1
capacity: 1
size: 2
capacity: 2
...
size: 8
capacity: 8
size: 9
capacity: 16
...
size: 16
capacity: 16
size: 17
capacity: 32
...
size: 24
capacity: 32

const_iterator和const的iterator:

迭代器可以理解为指针.

关联容器

关联容器和顺序容器的本质区别: 关联容器通过key存储和读取元素, 顺序容器通过位置存储和访问容器

关联容器map, set

参考链接及书目

欢迎关注个人微信公众号: wildDev

微信公众号二维码
上一篇下一篇

猜你喜欢

热点阅读