C++知识点总结(其他语法2-模板, 类型转换, C++11新特性)

模板(template)

泛型, 是一种将类型参数化以达到代码复用的技术, C++中使用模板来实现泛型
模板的使用格式如下:
template <typename\class T>
typename和class是等价的
模板没有被使用时, 是不会被实例化出来的.
例如:

template <class T>
T add(T a, T b) {
    return a + b;
}

template <class T, class A, class C>
C add(T a, A b) {
    return a + b;
}

int main() {
    add<int>(10, 20);
    add<double>(10.1, 2.2);
}

模版2-编译细节

模板的声明和实现如果分离到.h和.cpp中, 会导致链接错误.
编译, 链接原理
1.头文件不会参与编译, 因为头文件是拿来被包含的
2.编译器在编译时, 会对每一个cpp文件单独编译, 有一个add函数时, 通常把函数声明写在头文件add.h, 把函数实现
写在add.cpp文件(要#include “add.h”), 在main.cpp里面(#include “add.h”)即可, 这样的话在main.cpp里面有函数的声明, 没有函数的实现,
那么当使用add函数时, 因为有函数的声明所以不会报错, 但因为是单独编译, 所以main.cpp找不到函数的定义,
所以汇编代码是call <假的函数地址>, 在链接时, 才把假的函数地址修正成真的函数地址.
而如果有模板时, 因为编译add.cpp时, 不会生成具体的函数实现, 因为是单独编译, 不知道main里面的参数类型(没有被使用, 就不会生成函数实现),
所以不会生成具体的实现.所以在链接时, 就无法修正call 的函数地址.所以会导致链接错误
所以在写模板时, 不要把模版的声明和实现分离, 要放在同一个.h文件中
一般将模板的声明和实现统一放到一个.hpp文件(仍是头文件, 只是语义好一点)中, 在直接#include “add.hpp”

类型转换

C语言风格的类型转换符
(type)expression
int a = 10;
double d = a; // 隐式转换
C++中有4个类型转换符
static_cast
dynamic_cast
reinterpret_cast
const_cast
cast是转换的意思
使用格式:xx_cast(expression)

1.const_cast

一般用于去除const属性, 将const转换成非const
例如:

const Person *p1 = new Person();
// C++风格
Person *p2 = const_cast<Person *>(p1);
// C风格
Person *p3 = (Person *)p1;
// 这两种写法没有任何区别, 只是不同语言的写法而已

很多强制类型转换只是骗一下编译器, 本质其实就是赋值
相当于Person *p2 = p1;

2.dynamic_cast

一般用于多态类型的转换, 有运行时安全检测.
多态类型: 能完成多态功能的那几个类.
用法:

class Person {
    virtual void run() {}
};

class Student : public Person {

};
int main() {
    Person *p1 = new Person();
    Person *p2 = new Student();
    
    Student *stu1 = dynamic_cast<Student *>(p1);
    // 不安全, 因为相当于
    // Studnet *stu1 = new Person();就成了用子类指针
    // 指向父类对象, 不安全, 因为子类指针可以访问的
    // 范围超过父类对象所占的内存.
    Student *stu2 = dynamic_cast<Student *>(p2);
    // 安全, 相当于Student *stu2 = new Student();
}

dynamic_cast 可以检测到是否安全, 一旦检测到不安全, 直接让指针清空 = NULL, 变成空指针.

3.static_cast(了解, 开发中很少用)

1.对比dynamic_cast, 缺乏运行时安全检测
2.不能交叉转换(不是同一继承体系的, 无法转换)
3.常用于基本数据类型的转换, 非const转成const

int main() {
    int a = 10;
    double b = static_cast<double>(a);
    // 完全等价于double b = a;和double b = (double)a;
    
    Person *p1 = new Person();
    const Person *p2 = static_cast<const Person *>(p1);
    // 等价于    const Person *p2 = p1;
}

4.reinterpret_cast

1.属于比较底层的强制转换, 没有任何类型检查和格式转换, 仅仅是简单的二进制数据拷贝
2.语法限制:如果是不同类型之间的转换, 需要用引用, 仅仅是语法糖
3.可以交叉转换

int main() {
    int a = 10;
    double d = a;
    // 不是简单的二进制数据拷贝, 而是转换成浮点数的存储方式
    double d = reinterpret_cast<double&>(a);
    // 没有任何类型检查, 仅仅是简单的二进制数据拷贝.
    // 但double有8个字节, int有4个字节, 所以仅仅是
    // 将a的4个字节覆盖掉double的4个字节, double剩下
    // 的4个字节不管. 如果在栈空间默认是cc
}

C++11新特性

1.auto
可以从初始化表达式中推断出变量的类型, 大大简化编程工作

int a = 10;
auto a = 10; // 发现右边是整型, 所以a是int类型
auto str = "C++"; // const char *
auto p = new Person(); // Person*

属于编译器特性, 不影响最终的机器码质量, 不影响运行效率
2.decltype
decl = declear type 声明类型
可以获取变量的类型
int a = 10;
decltype(a) b = 20; // 相当于int b = 20;
3.nullptr
nullptr == null pointer 空指针
以后凡是清空指针都用nullptr不用NULL, 因为不专业
int *p = NULL
int *p1 = nullptr;
可以解决NULL二义性的问题
func(0);
func(nullptr);
func(NULL);
NULL -> #define NULL 0
4.快速遍历

int array[] = {1, 2, 3, 4};
for (int item : array) {
    cout << item << endl;
}
// 将array里面的元素挨个取出来赋值给item
// 等价于
for (int i = 0; i < 4; i++) {
    int item = array[i];
    cout << item << endl;
}

5.更加简洁的初始化方式
int array[]{1, 2, 3 , 4};
完全等价于
int array[] = {1, 2, 3, 4};

6.Lambda表达式

Lambda表达式(未完)


其他C++系列文章:

C++知识点总结(基础语法1-函数重载, 默认参数)
C++知识点总结(基础语法2-内联函数, const, 引用)
C++知识点总结(面向对象1-类和对象, this指针, 内存布局)
C++知识点总结(面向对象2-构造函数, 初始化列表)

C++知识点总结(面向对象3-多态)

C++知识点总结(面向对象4-多继承, 静态成员static)
C++知识点总结(面向对象5-const成员, 拷贝构造函数)
C++知识点总结(面向对象6-隐式构造, 友元, 内部类, 局部类)
C++知识点总结(其他语法1-运算符重载)
C++知识点总结(其他语法2-模板, 类型转换, C++11新特性)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章