0%

重学C++学习笔记(三)

上一篇,本篇主要是针对C++的新特性介绍,包括异常和智能指针

异常

异常函数,抛出异常用throw xxx;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 默认情况下表示可能会抛出任何异常
int divide(int v1, int v2) {
...
}

// 声明不会抛出任何异常
int divide(int v1, int v2) throw () {
...
}

// 声明会抛出const char *类型的异常
int divide(int v1, int v2) throw (const char *) {
if (v2 == 0) {
// 抛出异常
throw "不能除于0";

// 不会执行
cout << "后面当操作" << endl;
}
return v1/ v2;
}

try-catch捕获异常

1
2
3
4
5
6
7
8
try {
int *p = new int[999999];
} catch (int exception) {
cout << "捕获异常" << exception << endl
} catch(...) {
// 三个点表示可以捕获所有类型的异常
cout << "内存不足" << endl
}

throw异常后,会在当前函数中查找匹配的catch,找不到就终止当前函数代码,去上一层函数中查找。如果最终都找不到匹配的catch,整个程序就会终止

C++标准库提供了很多常用的异常,放在std命名空间下,开发中可以使用这些异常,让代码语义更明确,如下

  • std::exception
  • std::bad_alloc
  • std::bad_cast
  • std::bad_exception
  • std::bad_typeid
  • std::logic_error
  • std::domain_error
  • std::invalid_argument
  • std::length_error
  • std::out_of_range
  • std::runtime_error
  • std::overflow_error
  • std::range_error
  • std::underflow_error

智能指针

由于函数执行的流程不可预测,异常处理会带来内存管理的问题,C++引入智能指针解决内存管理的问题

  • auto_ptr(已废弃):自动管理内存的释放

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <memory>
    #include <string>

    {
    // 1. 创建一个auto_ptr指针变量p1,当p1释放时,"hello"也会被释放,不需要手动释放hello字符串
    auto_ptr<string> *p1 = new string("hello");

    // 不需要手动释放,当变量p1释放的时候(栈空间被回收),就会自动释放
    // delete p1;


    // 2. p1将所有权转让给p2,p1会指向nullptr,而p2指向"hello"
    auto_ptr<string> p2 = p1;
    }
  • unique_ptr:自动管理内存释放,且内存只能有一个指针变量引用,不支持赋值和复制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <memory>

    std::unique_ptr<Task> p1(new string("hello"));

    // 通过 unique_ptr 访问其成员
    string *s1 = p1.get();

    // 编译不通过,不能支持赋值
    std::unique_ptr<Task> p2 = p1;

    // 可以通过std::move转移所有权到p2,此时p1指向nullptr
    std::unique_ptr<Task> p2 = std::move(p1);
  • shared_ptr:通过引用技术共享对象,对象内部存储引用技术,当引用技术为0的时候,则进行析构,与ObjC的ARC技术类似

    1
    2
    3
    4
    5
    6
    shared_ptr<string> p1(new string("hello"));
    // 引用计数为1
    cout << p1.use_count() << endl;
    auto p2 = p1
    // 引用计数为2
    cout << p1.use_count() << endl;
  • weak_ptr:由于shared_ptr存在循环引用的问题,weak_ptr用于弱引用对象(引用计数不加1)与strong_ptr配合使用

自己实现auto_ptr指针,通过栈上的局部变量管理

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
template <typename T>
class SmartPoint {
private:
T *m_obj;
public:
SmartPoint(T *obj): m_obj(obj) { }
~SmartPoint() {
if (m_obj != nullptr) {
delete m_obj;
}
}

// 重写变量访问
T *operator->() {
return m_obj;
}
}

void main() {
{
// 由于p是存放在栈空间,当p释放的时候,内部的Person也会被释放
SmartPoint<Person> p(new Person(20));
p->run();
}
}

模板(泛型)

C++通过模板实现泛型,模板其实是相当于语法糖,在编译的时候自动生成对应的方法,如果方法没有使用,则不会被编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 模板方法-单参数
// template <class T> // 也可以使用class,是等价的
template <typename T>
T add(T a, T b) {
return a + b;
}

int result = add<int>(10, 20);

// 模板方法,多参数
template <typename TA, typename TB> T add(TA a, TB b) {
return a + b;
}

// 模板类
template <typename T>
class Array {
T *m_data;

void add(T t) {

}
}

编译细节

  • 所有的.cpp文件都是单独编译的,编译成.obj目标文件,而对于外部符号(例如头文件的声明的符号)都通过占位的方式进行编译
  • 编译完成后,通过链接器linker,链接所有的.obj,把所有的占位符号都修复成成真实的地址

为了解决模板编译的问题,把头文件改为.hpp,hpp文件既包含声明也包含实现,通常用来定义模板类和模板方法

C++新特性

这里只列出一些常用的

  1. auto关键字:自动识别类型,和其他语言的var差不多

    1
    2
    auto person = new Person();
    auto a = 10;
  2. decltype: 编译器特性,获取变量的类型

    1
    2
    3
    int a = 10;
    // 相当于:int b = a + 1
    decltype(a) b = a + 1;
  3. nullptr: 空指针,解决NULl二义性问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void func(int a) { }
    void func(int *a) { }

    // 存在二义性问题
    func(NULL);

    // 用下面方式调用
    func(0);
    func(nullptr);
  4. for快速遍历

    1
    2
    3
    4
    int items[] = {1, 2, 3};
    for (auto item: items) {
    ...
    }
  5. lambda表达式: [capture] (params) mutable exception -> returntype { body }

    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
    int g = 10;
    int h = 20

    // 捕获局部变量g,参数为int,返回值为void
    void (*p)(int) = [g] (int a) -> void {
    cout << a << g << endl
    };

    // 可以用auto,自动识别类型,用=隐式捕获用到的变量
    auto p1 = [=] (int a) -> void {
    cout << a << g << endl
    };

    // 引用捕获
    auto p1 = [&g] (int a) -> void {
    cout << a << g << endl
    };

    // 隐式引用捕获,g为值捕获
    auto p1 = [&, g] (int a) -> void {
    cout << a << g << endl
    };

    // 值捕获变量修改,添加mutable,可以修改捕获变量,修改了也还是值捕获,不会作用到外部
    auto p1 = [g] (int a) mutable -> void {
    g++;
    cout << a << g << endl
    };

    // 如果能推断返回值类型,也可以返回值
    auto p2 = [](int a){
    cout << a << endl
    return a;
    };

    p();
    p2();