C++学习笔记
C++里面比较重要、或比较难记住、或比较复杂的一些知识点
一些太简单的就没写在这里了
页码来自C++ Primer Plus(第6版)中文版
函数模版
格式
template <typename T>
T f(T a);
//另一种写法
template <class T>
T f(T a);
templa <class t1, class t2> f(t1 a, t2 b);
- 函数模版重载与普通函数重载一样
显式实例化 p288
- 使用模版生成函数定义
template void swap<int> (int, int);
//另一种写法, 因为编译器可以从后面的参数自动判断类型,所以可省略
template void swap<> (int, int);
具体化 p288
完全具体化
- 不要使用模版生成的定义, 而应使用专门的函数定义
template <> void swap<int> (int, int);
template <> void swap<> (int, int);
- 可以省去函数名后面尖括号里的类型名字
- 具体化template后面有一个空尖括号
- 实例化还必须提供定义, 另外定义和在声明处定义均可
部分具体化
- 部分限制模版通用性
template <class T1, class T2>
void tf(T1 a, T2 b)
{
cout << a + b << endl;
}
template <class T1>
void tf(T1 a, int b)
{
cout << "fuck";
cout << a + b << endl;
}
- 但全部类型都被指定了(完全具体化), 前面的尖括号就变空了, 这就是完全具体化前面空尖括号的意思
非类型模版参数
template <class T1, int n>
void f(T1 a)
{
cout << a + n << endl;
}
- 调用时必须强制选择
f<double, 10>(10.10);
否则会报错 - 这个好像并没有什么用, 看完类模版的非类型参数用函数模版试了一下, 发现也行
匹配 p289
- 编译器在选择函数是根据下面这三个的顺序寻找的
- 最匹配 (完全匹配>提升转换>标准转换(int->char, long->double)>用户定义的转化)
- 非模版 (如果多个函数匹配程度相同, 非模版函数优先)
- 最具体
强制选择 p293
Swap<>(a, b);
强制使用模版函数(而不是非模版函数)
Swap<int>(a, b);
强制使用int实例化的模版函数
关键字 decltype(c++11) p295
- 为表达式, var与结果值类型相同
- 为函数, var与函数返回值类型相同(包括const, &) 如果expression用括号括起来了
- var类型参考上面两条, 再变成引用
decltype( expression ) var;
如果expression
int a;
decltype( (a) ) b;//b为int&
后置返回类型(c++11) p297
- 如果返回类型需要由参数决定, 但返回类型在参数之前, 此时参数类型还没确定, 只能用后置返回类型
auto f(int a, double b) ->double;
template <class p, class t> auto f(p a, t b) ->decltype(a+b);
存储说明符
auto(c++11后不再是说明符), register, static, extern, thread_local, mutable
register p309
-
c及c++11之前:
- 建议编译器使用寄存器存储变量 c++11:
- 不再有上面的功能, 显式指出变量为自动的, 避免使之前的代码非法
static 变量链接性, 作用域, 持续性 p310
存储描述 | 持续性 | 作用域 | 链接性 | 声明 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 无 | 代码块中 |
寄存器 | 自动 | 代码块 | 无 | 代码块register |
静态无连接性 | 静态 | 代码块 | 无 | 代码块static |
静态外部链接性 | 静态 | 文件 | 外部 | 函数外, 不加static |
静态内部链接性 | 静态 | 文件 | 内部 | 函数外, 加static |
静态变量自动初始化为0, 指针初始化为空指针
extern 要使用其他文件中的变量, 必须用extern在该文件中用extern声明
file1 : int a;
file2 : extern int a;
mutable
- 在类或结构体中使用, 表明即使这个类的对象为const, 这个值也能被修改
struct d
{
mutable int a;
int b;
};
const d a;
a.a = 20;//依旧可以被修改
a.b = 20;//非法
cv限定符 consst, volatile p317
const 内部链接性 p318
- const 类型的全局变量为内部链接性, 不与其他文件共享
- 可以使用extern使其变成外部链接性
extern const int a;
volatile
- 表明即使程序代码没有对这个变量进行修改, 它的值仍然可能会改变
硬件或其它程序可能会对它进行修改
函数链接性
static 表明函数只在这个文件中可见
static int f(int a);
语言链接性 p319
extern "C" void f(int a);
extern "C++" void f(int a);
定位new运算符 p321
- 需要包含new头文件
- 指定要使用的内存位置(普通new在堆中分配内存)
- 不需要使用delete释放内存
char buf[1000];
double * pd = new (buf) double[12];
类与对象
类声明大括号后面要分号
- 编译器会自动添加
{};
, 导致很容易忘记后面有分号, 遇上不会自动补全的编辑器就容易忘记敲 - 结构也一样
inline
- 在类声明中定义的函数都自动成为内联函数(如果可以的话)
- 也可以在声明中加inline使其成为内联函数
对象数组初始化
class A
{
//定义
};
A a[10] =
{
A(arg1, arg2);
A();
//可对不同元素使用不同构造函数
...
}
作用域为类的常量 const p372
- 不能使用const, 因为类声明的时候没有给它提供存储空间, 只有创建对象时候才有空间
虽然可以在类声明里定义一个const int a;不会报错
但只要你在类声明里使用这个a, 就会报错, 因为这个时候(声明时)还不存在a
可使用 static const 或者 enum - 在类那使用非静态const常量时候, 只能在声明处给出它的值或创建对象时使用构造函数设置它的值, 之后便不能再更改
类中的static静态变量
- 在类中声明了一个static静态变量,使用它前,必须显式定义它
例如:
class A
{
private:
static int a[10];
};
//显式定义
int A::a[10];
//如果没有显式定义, 会出现:undefined reference to `A::a|
enum
enum class egg{small, medium, ...}
class限定作用域在类内, 防止出现两个small等等情况冲突enum class : short egg ...
显式指定egg enum底层为short, 不然底层由不同实现决定(不同编译器会出现不同的情况)
运算符重载 p387
- 注意事项
- 不能违反运算符原来的句法规则(参数个数不能改变)
- 不能改变运算符优先级
- 不能创建新的运算符
-
不能重载的运算符:
-
sizeof
.
(成员)
.*
(成员指针)
::
(作用域解析符)
?:
(三目运算符)
typeid
(RTTI运算符)
const_cast dynamic_cast reinterpret_cast static_cast
(强制类型转换运算符)
-
只能通过成员函数重载的运算符
-
=
()
[]
->
类的强制与自动类型转换 p413
- 接受一个参数(只有一个参数或对第一个参数之后的参数提供默认值)的构造函数可以实现类的自动类型转换
关键字explicit
- 关闭隐式转换
explicit ClassName() {}
二次转换
- 当不存在二义性的时候, 会自动进行二次转化
如
class A
{
A(double a) .....
};
A a;
a = 10;//会自动进行二次转化, 合法
- 如果还有一个A(long)构造函数就会出现二义性, 不会自动进行二次转换
转换函数(将类转化成其它类型)
operator type();
例如: 类的成员函数operator int() const;
可以讲类转换成int
同样可以使用 explicit
关闭隐式转换
复制构造函数 p432
- 将一个对象复制到新创建的对象中去
- 只在新建一个对象并且初始化为同类对象的时候才会被调用
- 默认复制构造函数只进行浅复制(例如只复制指针, 而不复制指针指向的内存空间)
赋值运算符
operator =
默认复制运算符也和默认构造函数一样, 只进行浅复制
静态 类成员函数 static p441
- 可以将成员函数声明为静态
- 静态成员函数不能通过对象调用(编译器实测可以, 但是C++PP说不可以, 想想通过对象调用静态函数也没什么意义, 静态函数不能访问非静态成员), 只能通过类名和作用域解析运算符::来调用它
(如果该函数是public)
class A
{
static int f();
};
A a;
a.f(); //错误(编译器实测可以, 但是C++PP说不可以, 想想通过对象调用静态函数也没什么意义, 静态函数不能访问非静态成员)
A::f(); //正确
- 静态成员函数只能访问静态成员
在构造函数中使用new的注意事项
- new一个变量建议使用 int *a = new int[1];(以int为例)
这样delete的时候使用delete [] a; - 如果使用new int;delete的时候却使用了delete会出问题
继承
基类指针或引用可以直接指向派生类对象
- 基类指针只能调用基类成员, 即使指向派生类对象, 也不能调用派生类成员
虚函数 关键字virtual p493
- 使用关键字virtual, 通过指针或引用调用成员时候, 将根据指针或引用指向的对象决定调用哪个方法
- 否则, 将根据指针或引用的类型决定
- virtual 应该在基类中使用, 因为需要使用虚函数特性的情况都是使用基类指针或引用调用方法
- 派生类重新定义的虚函数前也可以加上virtual, 但不是必要的
- 可以使用override(C++11)显式指出派生类重新编写的基类虚函数