文章目录
模板(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表达式(未完)