C++学习笔记

2018年1月12日 0 条评论 132 次阅读 0 人点赞

目录

很久以前写的了, 原来是写在在CSDN上面, 重新整理了一下, 现在也放到这里

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

  • 编译器在选择函数是根据下面这三个的顺序寻找的
  1. 最匹配 (完全匹配>提升转换>标准转换(int->char, long->double)>用户定义的转化)
  2. 非模版 (如果多个函数匹配程度相同, 非模版函数优先)
  3. 最具体

强制选择 p293

Swap<>(a, b); 强制使用模版函数(而不是非模版函数) Swap<int>(a, b); 强制使用int实例化的模版函数

关键字 decltype(c++11) p295

decltype( expression ) var;

  1. 如果expression : 为表达式, var与结果值类型相同 : 为函数, var与函数返回值类型相同(包括const, &)

  2. 如果expression用括号括起来了 : var类型参考上面两条, 再变成引用

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

  • 注意事项
  1. 不能违反运算符原来的句法规则(参数个数不能改变)
  2. 不能改变运算符优先级
  3. 不能创建新的运算符

不能重载 的运算符:

: 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)显式指出派生类重新编写的基类虚函数

虚析构函数

  • 使用虚析构函数可以保证使用指针delete删除动态分配内存的对象时, 对象能够被正确地delete

动态联编和静态联编 p503

  • 虚函数只有在运行阶段才能确定调用哪个定义
  • 动态联编为每个对象添加一个隐藏成员, 一个指向虚函数表的指针, 虚函数表存储了虚函数地址
  • 动态联编效率略低

构造函数不能是虚函数

析构函数应当是虚函数

友元函数不能是虚函数

  • 友元函数不是类成员函数, 但可以让友元函数调用虚成员函数

在派生类中重新定义将会隐藏基类方法, 而不是作为函数重载

  • 重新定义基类函数确保函数原型相同
  • 但返回类型可以改变而不会被认为是重新定义而隐藏基类方法 p506

protected

  • 在外界相当于private, 在派生类中相当于public

派生类使用基类函数访问基类私有成员

  • 使用函数形式调用符号, 例如: operator=(a, b), 这样可以对其使用作用作用域解析符:: : 如果派生类也定义了=, 那么调用基类=应该使用baseClass::operator=();, 直接使用=, 将调用派生类的=, 其他符号也一样

派生类的友元函数使用基类友元函数访问基类私有成员

  • 使用强制类型转化, 调用基类友元函数
std::ostream & operator<<(std::ostream &os,  const hasDMA & hs)
{
    os << (const baseDMA &)hs;
    os << "Style: " << hs.style << std::endl;
    return os;
}

抽象基类(ABC) p509

纯虚函数

  • 在函数声明末尾加上 = 0;
  • 指出类是抽象基类
  • 无需提供纯虚函数的定义, 也不能给纯虚函数定义

抽象基类不能用于声明对象

  • 包含纯虚函数的类(抽象基类)只能用于作基类

三种继承方式 p549

特征 公有继承 保护继承 私有继承
公有成员变成 派生类的公有成员 派生类的保护成员 派生类的私有成员
保护成员变成 派生类的保护成员 派生类的保护成员 派生类的私有成员
私有成员变成 只能通过基类接口访问 只能通过基类接口访问 只能通过基类接口访问
能否隐式向上装换 是(但只能在派生类中)
  • 三种继承中, 派生类都不能访问基类的private成员, 都能访问基类的public和protected成员
  • 区别是保护继承使public变成派生类的protected成员, 外界不能访问, 派生类的派生类依旧可以访问
  • 私有继承使public和protected成员都变成派生类的private成员, 外界不能访问, 派生类的派生类也不能访问

使用using重新定义访问权限 p550

  • 在私有和保护派生类公有区域使用using baseClass::f; 使公有或保护成员在外界可访问 例如
class A
{
public:
	void f();
	void h();
};
class B :private A
{
public:
	using A::f;
};
...
B bb;
bb.f();//合法, 因为使用了using使A中的f可访问
bb.h();//不合法, error: 'void A::h()' is inaccessible|

多重继承 p567

虚基类

  • 在派生类中使用virtual将基类设置为虚基类, 可以使多个包含基类的派生类派生出的派生类只继承一个原始基类
class Worker;
class Singer :virtual public Worker {...};
class Waiter :virtual public Worker {...};
class SingerWaiter :public Singer,  public Waiter {...};//SingerWaiter类中只包含一个Worker基类
  • 有间接虚基类的派生类包含直接调用间接基类构造函数的构造函数, 这在间接非虚基类中是非法的
SingerWaiter(...) :Worker(wk),  Singer(wk,  s),  Waiter(wk,  w) {}
//调用了两个直接基类的构造函数, 但两个直接基类的构造函数虽然接受用以初始化Worker成员的wk, 但这两个函数不会初始化Worker类
//还直接调用了Worker的构造函数, 用以初始化Worker, 这在非虚基类中是非法的
//如果没有调用Worker的构造函数, Worker将不会被初始化为期望的值(会调用默认初始化函数)
  • 如果基类, 虚基类, 派生类有相同名称方法, 按派生类>直接基类>间接基类的优先级调用函数, 可以使用::运算符调用哪个方法

类模版 p568

格式

  • 类似于模版函数
template<class Type>
class A {};
//另一种写法
tmeplate<typename Type>
class A {};

A<int> a;//实例化
  • 模版类的声明和实现一般不能分开放在.h和.cpp文件中, 建议都放在头文件(或同一个cpp)中
  • 因为编译器(complier)编译(compile)时只能了解到当前cpp文件(经过包含头文件等预处理流程)的内容, 编译器能看到头文件中模版的声明, 但看不到你在另一个cpp文件中的定义, 所以编译器会将寻找实现的任务交遗留链接器(linker); 然而, 在另一个cpp文件中模版类成员函数的定义由于它是一个模版, 并不会被编译器编译成二进制文件; 所以在链接(link)时, 链接器找不到模版函数的实现, 就会出现undefined reference to xxx 的错误
  • 而如果将声明和实现都放在同一个文件中, 编译器编译时遇到调用模版类, 便会寻找它的声明, 并将它实例化, 此时因为实现也在头文件中(此时已经过预处理放到了cpp中), 所以会将它编译成二进制文件
  • 我觉的我这一段写的特别好^_^
  • 模版类的类限定符
//成员函数在类外面定义时要加上模版前缀和类限定符
template <class Type>
class A
{
    void f();
    ...
};

template<class Type>
void A<Type>::f() {...}

非类型参数 p577

template <class Type,  int n>
class A {...};
//int n为非类型参数
  • 非类型参数只能是整型, 枚举, 指针, 引用 template <class Type, double d> class {...}; //error: 'double' is not a valid type for a template non-type parameter

  • 可以只有非类型参数 template <int n> class {...};

递归使用模版 p579

Array< Array<int,  10>,  5> arr;//arr含有5个类型为Array<int,  10>的元素
相当于arr[5][10];

模版参数默认值 p582

template<class T1, class T2 = int, int n = 10> class {...};

  • 和函数参数默认值一样

类模版实例化和具体化 p582

  • 类模版实例化和模版函数一样
  • 显式实例化template class Array<string, 100>;
  • 类模版具体化类似于函数模版

完全具体化

template <class T>
class A {...};
//当类型为int时候, 不要使用模版生成的类, 而使用专门为int定义的类
template <> class A<int>//与模版函数不同, 类名后面尖括号中的类型名不能省略
{
...//A<int>的专门代码
}

部分具体化

  • 和函数模版一样
template <class T1,  class T2>
class C { };
//部分具体化
template <class T1>
class C<T1,  int> {};

匹配

  • 如果有多个模版匹配, 将使用最具体的模版

成员模版 P584

  • 模板类和模版函数可作为结构, 类, 模版类的成员
  • 格式
template <class T1>
class C
{
public:
    template <class T2>//模版类成员
    class D
    {
        //...
    };
    
    template <class T3>//模版函数成员
    T1 f(T3 a)
    {
        //...
    }
    //...
};
  • 在声明外定义
template <class T1>
class C
{
public:
    template <class T2>
    class D;
    template <class T3>
    T1 f(T3 a);
};

template <class T1>
    template <class T2>
    class C<T1>::D//用C<T1>::指出D是类模版C的成员
    {//...
    };
template <class T1>
    template <class T3>
    T1 C<T1>::f(T3 a)
    {//...
    }
  • 必须用C<T1>:: 指出D是类模版C的成员
  • 必须将template <class T1>template <class T2> 分开写, 因为模版是嵌套的, 不能写成 template <class T1, class T2>
  • template <class T1>template <class T2> 之间的换行和缩进不是必须的

将模版作为参数 p586

  • 格式
template< template<class T1> class Thing,  class T2,  int n>
class C
{
//...
};
  • 模版类C有三个参数, 模板类Thing, 类型参数T2, 非类型参数n
  • 调用: C<stack, double, 10> c;
  • 模版参数应该用模版 stack , 而不能用 stack<int> , 前者是模版, 后者是实实在在的类(实例化的模版类), 如果要用后者, 模版参数应该是普通的类型参数(class T)

模版类和和友元函数 p588

  • 书中将模版类的友元分成三类
  1. 非模版类友元 (非模版函数)
  2. 约束模版友元 (模版函数)
  3. 非约束模版友元 (模版函数)

非模版友元函数 p588

  • 例如
template <class T>
class C
{
public:
    friend void f1();
    friend C<T> f2(C<T>);
    //...
};
//友元函数定义
void f1()
{//...
}
C<int> f2(C<int> a)
{//...
}
C<double> f2(C<double> a)
{//...
}

  • f1, f2都是类模版C的非模版函数友元函数
  • 两个函数都不是非模版函数
  • f2的参数即使含有一个待定类型T, 但仍旧不是模版函数, 因为在实例化前, 这个类并不存在, 这个f2的声明也不存在; 而实例化之后, f2中的T也变成了相应的类型, 不在含有待定类型, 例如:
  • C<int> c; 将生成一个C<int> f2(C<int> ); 声明, 调用f2时, 编译器将会寻找C<int> f2(C<int>)的函数定义
  • 所以, 对于所有可能的类型, 都应该单独给出该类型友元函数的定义, 如上代码中f2的int和double两个定义.
  • 编译器会给出警告, 提示这个函数不是模版函数: warning: friend declaration 'C<T> f2(C<T>)' declares a non-template function [-Wnon-template-friend]

模版约束友元函数 p590

  • 例子:
template <class T1>//定义可以放在后面, 但声明必须在模版类之前
T1 f(T1 a)
{
//...
}
//模板类
template <class T>
class C
{
public:
    friend C<T> f<>(C<T>);//约束模版友元函数
};
  • 这类友元函数本身是模版函数, 类模版实例化后, 每个实例将会获得一个相应类型的友元函数, 例如 C<int> c;实例化得到了一个C<Int>类以及他的对象c, C<int> 类将会包含一个C<int> f(C<int>); 友元函数;

  • 函数f本身就是一个普通的模版函数, 不需要对象即可调用

非约束模版友元函数 p592

  • 例如:
template <class T>
class C
{
public:
    template <class T1>
    friend T1 f(T1 a);
};
//函数定义
template <class T1>
T1 f(T1 a)
{
}

  • 函数f是模版类的一个模版成员, 它本身就是模版
  • 类模版实例化后, 每个实例都会的到一个函数模版成员, 例如: C<int> c; 实例化的到了一个C<int> 类和对象c, C<int> 拥有一个函数模版成员friend T1 f(T1 a);

参考*A嘴角上扬 的BLOG: 模板类与友元”那些事(C++)*

模版别名(using =) p593

typedef stack<int> s_int;
等效于:
using s_int = stack<int>;
之后便可以使用s_int定义stack<int>对象

另一种用法
template<class T>
using a_100 = array<T, 100>;
a_100<int> a; //等效于:arrary<int, 100> a;

C++11还允许将using = 用于非模版
typedef long long ll;
using ll = long long;//c++11 only

友元 p603

友元类

  • 一个类可以成为另一个类的友元, 例如:
class A
{
	friend class B;//B是A的友元, 可以访问A中的一切成员
}
class B
{
}
  • 友元函数可以放在类的private, public, protected中的任何位置, 不会影响访问权限

友元成员函数 p607

  • 可以只让类的某个成员函数成为另一个类的友元, 和普通友元函数差不多, 只不过这个函数是一个函数的友元函数的同时, 还是另一个函数的成员函数
  • 例如:
class Class1;//前向声明;
class Class2
{
    void f(Class1 a);//用到了Class1, 所以Class1必须使用前向声明
}
class Class1
{
	friend void Class2::f(Class1 a); //用到了Class2的方法f, 所以f必须已经声明了
}
  • 使用的任何函数或类之前必须声明他们, 但不必定义
  • 可以使用前向声明来声明类, 告诉编译器这是一个类, 而不必定义
  • 如果Class2的方法f的实现用到了Class1成员或方法, 那么f的实现必须在Class1定义之后, 因为此时f所用到的Class1中的成员还没有被定义, 但可以Class2类的定义中先声明f的原型, 因为f的原型声明不需要用到Class1的成员

互为友元

  • 两个类可以互为友元
  • 声明友元类并不需要前向声明那个类, 因为语句friend class className; 已经指明了className是一个类, 相当于已经前向声明了 例如:
class A
{
    friend class B;
    //...
}
class B
{
    friend class
    //...
}

共同的友元 p610

  • 如果一个函数需要同时访问两个类的私有数据:
  1. 让这个函数成为一个类的成员, 同时成为另一个成为另一个类的友元函数
  2. 让这个函数成为两个类的友元
  • 例如:
class B;//B的前向声明
class A
{
    friend void f(A a, B b);//这里用到了类B, 所以地第一行需要前向声明B
}
class B
{
    friend void f(A a, B b); 
}

异常

关键字try throw catch p619

  • 例如:
void f(int a)
{
	if(a < 0) throw "erro";
	//...
}
int main()
{
	try
	{
		f(-1);
	}
	catch(const char * s)
	{
		printf("%s", s);
		//...
	}
	//...
}
* try和catch后面必须要有大括号

栈解退 p625

  • 出发throw语句后, 程序将会按照入栈的顺序, 不断释放栈向下寻找catch语句, 如果没有找到, 则会调用默认terminate(), terminate()再调用abort()结束程序, 在G++中,屏幕上会输出如下内容:
terminate called after throwing an instance of '/*这里是异常类型*/'

wiki: 如果当前函数内的所有try...catch...块都不能匹配该异常,则递归回退到调用栈的上一层函数去处理该异常。如果一直回退到主函数main()都不能处理该异常,则调用系统函数terminate()终止程序。 来自: C++异常处理

引用异常类型

  • 如果catch参数为引用, 将会创建一个临时对象(作用域为当前catch块), 并让这个引用指向该临时对象

catch类型匹配

wiki: catch语句匹配异常对象时,不会做任何隐式类型转换(implicit type conversion),包括类型提升(promotion)。 异常对象与catch语句进行匹配的规则很严格,一般除了以下几种情况外,异常对象的类型必须与catch语句的声明类型完全匹配:允许非const到const的转换;允许派生类到基类的转换;将数组和函数类型转换为对应的指针。 来自: C++异常处理#catch

异常规范(C++11已摒弃) p624

void f(int a) throw(char *);
void f(int a) throw();//指明不会抛出异常
  • 已摒弃但还能用

noexcept(C++11)

void f(int a) noexcept; 指明不会引发异常

exception和stdexcept p631

  • 标准库中的异常类
  • 含有接受一个string的构造函数和返回一个字符串的what函数

new的异常 p633

  • 在新的C++标准中*(C++PP中没有指明是哪个标准开始的, 但是使用G++测试是从C++98标准开始)* new内存分配失败不在放回空指针, 而是抛出std::bad_alloc异常, 在G++中what()返回字符串"std::bad_array_new_length";
  • 指定new返回空指针, 用法:
int * pi = new(std::nothrow) int;
int * pa = new(std::nowthrow) int[100];

异常类继承

  • 类型为基类的捕获语句可以捕获基类及其派生类, 所以, 要使派生异常类被捕获, 应该使捕获基类的语句放在最后

terminate()和set_terminate()

  • 未被捕获的异常不会立即导致程序结束, 而是会调用terminate()函数, 而terminate
  • 可以使用set_terminate()改变terminate的行为, 但程序仍然会终止
  • set_terminate()接受一个void类型的函数指针

RTTI

  • 全称运行阶段中类型识别(Runtime Type Identification)

dynamic_cast

  • 类型转换运算符, 判断能否安全的将对象的地址赋给特定类型的指针
  • 用法:
Base * pb = dynamic_cast<Base *> (pg);//pg能否安全地转换为Base*, 如果能, 返回地址, 否则返回空指针
  • 如果pg是Base类或Base派生类的指针, 则可以安全转换

dynamic_cast用于引用

  • 当dynamic_cast用于引用的时候, 由于无法通过特殊的返回值来指示失败, 所以当无法安全转换时, 将会抛出bad_cast(exception的派生类, 在头文件typeinfo中定义)类型异常
#include <typeinfo>
//...
try
{
	Base & rb = dynamic_cast<Base &> a;
}
catch(bad_cast & c)
{
	//...
}
//...

typeid运算符和type_info类

  • typeid返回一个type_info对象, type_info重载了==!=运算符, 可以使用这两个运算符对类型进行比较, 如果类型相同, 返回true, 反之则反
typeid(Type1) == typeid(Type2);
typeid(Type *) == typeid(Type); //false
typeid(Type &) == typeid(Type); //true
  • typeid同一类型的引用和对象比较返回true, 同一类型的指针和对象则返回false
  • type_info类有一个name()成员, 返回一个字符串, 内容随实现而异, 在G++中, 返回对象类型名称, 指针则在名称前面加上大写字母P, 如果是用户自定义对象则返回一个数字加类名, 例如typeid(int).name()返回"i", typeid(int *).name()返回"Pi"等等

强制类型转换

dynamic_cast

  • 使能够在类层次结构中进行向上转换, 而不允许其他转换
  • 只能用于指针和引用

参见上一章: dynamic_cast

const_cast

  • const_cast运算符只能用来移除(或添加, 添加直接加就好了不需要这个运算符)cv限定符const和volatile的限定, 而不允许改变其他方面

  • 对于常量, 即使移除了const限定符, 也不能改变该常量的值

  • 对于变量, 去除了const的指向变量的带const限定的指针可以改变常量的值

  • 只能用于指针或引用

  • 这个运算符一般有两种用处

  1. 当函数参数是非const指针或引用, 但能保证其不会更改参数, 可以使用const_cast移除const后将常量地址传入函数, 或者const对象使用const_cast移除const后用来调用非const成员方法
  2. 将指向常量但带有const限定的指针去除const, 使得可以用转换后的指针修改常量
  • 例如:
//...
void f(int * a)
{
    *a += 100;
    cout << *a << endl;
}
//...
const int ci = 10;//常量
int i = 20;//变量
const int * cpci = &ci;//const限定指向常量的指针
const int * cpi = &i;//const限定指向变量的指针
f(const_cast<int *> (cpci));//合法
cout << ci << endl;
*const_cast<int *>(cpci)+=100;//合法, 但不会有效果
cout << ci << endl;
*const_cast<int *>(cpi)+=200;//合法, 有效果
cout << i << endl;
const_cast<long long *>(cpi);//不合法, 只允许修改cv限定, 其他改变即使是无害的提升转换或派生类向基类转换的向上转换也不允许

//...
//G++编译运行输出为:
//110
//10
//10
//220
//可以看出, 虽然在函数f内对常量+100产生了效果, 但回到主函数后常量没有变化
//对常量修改没有效果, 对变量修改才有效果
  • 上面有很多都是未定义行为, 在不同的编译器中产生的结果可能不同

参考了C++标准转换运算符const_cast, Idel的博客

static_cast

  • static_cast 运算符可用于将指向基类的指针转换为指向派生类的指针等操作。 此类转换并非始终安全。
  • 通常使用 static_cast 转换数值数据类型,例如将枚举型转换为整型或将整型转换为浮点型,而且你能确定参与转换的数据类型。 static_cast 转换安全性不如 dynamic_cast 转换,因为 static_cast 不执行运行时类型检查,而 dynamic_cast 执行该检查。 对不明确的指针的 dynamic_cast 将失败,而 static_cast 的返回结果看似没有问题,这是危险的。 尽管 dynamic_cast 转换更加安全,但是 dynamic_cast 只适用于指针或引用,而且运行时类型检查也是一项开销。 有关详细信息,请参阅 dynamic_cast 运算符。

来自static_cast 运算符

reinterpret_cast

  • 可以将完全不同类型的变量进行转换
  • 依赖于实现的底层

智能指针模版p667

  • 会自动释放动态分配的内存
    auto_ptr<int> pi(new int);//c++11摒弃
    
    unique_ptr<double> pd(new double);
    unique_ptr<P []> p(new P[10]);
    
    shared_ptr<string> ps(new string());
  • 构造函数声明为explicit, 所以不能直接将普通指针赋给智能指针, 要通过强制类型转换
    unique_ptr<double> pd;
    pd = unique_ptr<double>(new double);
  • 不要将指向非动态分配内存的地址赋给智能指针
  • 判断智能指针是否为空
unique_ptr<int> pi(new int);
//...
if(pi.get() == nullptr) //...

auto_ptr(c++11已摒弃)

  • auto_ptr只支持delete而不支持delete[]
  • 允许赋值给别的auto_ptr指针, 但将一个auto_ptr赋值给其他auto_ptr后, 原指针指向nullptr, 将auto_ptr作为参数传入函数也是一个赋值过程, 所以传入后原来智能指针指向nullptr

unique_ptr

  • 不允许直接赋值(临时右值的unique_ptr除外, 临时右值的unique_ptr可以赋给其他指针, 如函数的返回值)
  • 可以使用move()赋值或传入函数, 但之后原指针指向nullptr

shared_ptr

  • 可以赋值给另一个shared_ptr, 一块内存块可以有多个shared_ptr指针共享, 只有最后一个指针被删除时, 才会释放资源.
  • 只有当只有最后shared_ptr指针时, 使用reset才会释放资源
  • 可以将右值unique_ptr赋给shared_ptr

可以使用智能指针的指针或引用作为形参, 向函数传入智能指针

release和reset

  • release和reset, 都将智能指针设置为空, 但release只解除关联, 不释放内存, reset会释放内存, reset还有可以有指针参数, 释放原先关联的内存后, 将会关联到这个参数(只有对最后一个shared_ptr指针reset的时候才会释放其指向的资源)

swap和move

  • swap可以交换两个智能指针的值, 即使是unique_ptr
  • move可以将一个智能指针资源的所有权转移给另一个智能指针, 源指针指向nullptr

STL(基本)

string p656

构造函数

构造函数 描述
string(const char * s) 将string对象初始化为s指向的NBTS
string(size_type n, char c) 创建一个包含n个字符c的string对象
string(const string & str)
string()
string(const char * s, size_type n) 初始化为s前n个字符,即使超过n的结尾
string(Iter begin, Iter end)
string(const string & str, string size_type pos = 0, size_type n = npos
string(string && str)noexcept 移动构造函数(c++11)
string(initialzer_list<char>il) 初始化为初始化列表il中的字符(c++11)
初始化列表

string str = {'a', 'b', 'c', 'd'};

+=, +, =

string类重载了+=, =+, 它们的参数可以是char, NBTS, string

输入p659

这几个就够用了

char s[100];
cin >> s;
cin.getline(s, 100, '#');//一直读到遇到#字符位置, 如果第三个参数缺省默认值为回车, 将边界字符丢弃
cin.get(s, 100, '#');//同上, 但将边界字符保留在输入流中

string str;
cin >> str;
getline(cin, str, '#')//类似与NBTS的getline, 没有长度限制参数

find()

	//返回一个数字, 代表下标查找到的字符或字符串第一个字符出现的位置
    string s, t;
    int pos, n;
    //pos缺省值为0
    s.find(t);
    s.find(t, pos);//从pos位置开始查找
    s.find("abcd", pos);
    s.find("abcd", pos, n);//查找字符串前n个字符
    s.find('a', pos);
string头文件中其他相关方法
	//重载函数特征标都和find一样
    s = "abcdefgabcdef";
    t = "abcd";
    cout << s.rfind("ab") << endl;//查找最后一次出现的位置
    cout << s.find_first_of("ba") << endl;//查找参数中任何一个字符第一次出现的位置
    cout << s.find_last_of("dc") << endl;//同上, 但是最后一次出现的位置
    cout << s.find_first_not_of("abcd") << endl;//查找不是参数中任何一个字符的字符第一次出现的位置
    cout << s.find_last_not_of("cde") << endl;//同上, 最后一次出现的位置

compare

//返回值和strcmp类似, 相同0, 前面字符串更前返回负数, 否则正数
int compare(pos1, n1, const string & str, pos2, n2) const;//可以省略第二个字符串后面的参数
int compare(pos1, n1, const char * s, n2) const;
==, <=, <, >=, >, !=//和compare一样, 都可以用于string对象和c风格字符串

其他string方法

  • capacity() [kəˈpæsɪti] 返回对象实际占用内存大小, string占用内存大小并不等于size()*sizeof(char), 因为如果每次增加字符都要重新分配内存效率太低, 所以每次遇到内存不够了, 将分配一个原先两倍大小的内存块
  • reserve(size\_type n); resize(size\_typde n); resize(size\_type n, char ch)改变实际占用内存大小, 其中reserve不能截短对象大小,第二个resize可以将扩张的部分全部填充为字符ch
  • c_str()方法返回一个指向C风格字符串的const指针(不能修改), 并保证是以'\0'结尾, (并不保证是string保存字符串的实际位置, 可能只是一个副本), 然后就可以完全当成C风格字符串来使用了, 但是当string对象本身被修改后可能原来的指针可能会无效
  • data()方法与c_str()类似, 但不保证会以'\0'结尾
  • shrink_to_fit() 请求让capacity()的值与size()的值相同C++11
  • clear() empty()
  • at()类似与[], 但会执行下标检查等, 安全性>[], 但效率<[];
  • substr(size_type pos = 0, size_type n = npos) const返回一个从pos开始长度为n的子字符串string对象
  • front() back() 返回第一个和最后一个元素
  • append(), assign() 分别能给string对象追加字符串, 和给string对象赋值, 参数同之前的一系列函数一样, 可以是string对象也可以是c风格字符串

迭代器 p685

  • 迭代器类型688
  • 指针
  • ostream_iteratoristream_iterator
ostream_iterator<int, char> out_iter(cout, " ");
istream_iterator<int, char> in_iter(cin);
istream_iterator<int, char> in_iter();//表示输入失败(文件尾等)
  • 反向迭代器
执行++后会往前, 相当于反向了
reverser_iterator
for(set<int>::reverse_iterator r_iter = S.rbegin(); r_iter != S.rend(); ++r_iter)
	cout << *r_iter << " ";
const_reverser_iterator
rbegin()
rend()
crbegin()
crend()
for_each(S.rbegin(), S.rend(), [](int i){cout << i << " "; });
  • insert迭代器
插入用,不会覆盖, 普通迭代器不会分配空间, 如果空间不够会产生错误
back_insert_iterator
front_insert_iterator
insert_iterator//有一个inserter的包装
copy(S1.begin(), S1.end(), insert_iterator<set<int>>(S, S.begin()));
//inserter简化代码
copy(S1.begin(), S1.end(), inserter(S, S.begin()));

容器695

序列容器697 关联容器702

函数

for_each() random_shuffle() sort() p681 copy() swap()

函数对象p707

  • 重载了()运算符的类或结构
  • 可以使用类成员变量来传递参数值, 而不是用参数

函数适配器p711

Lambda函数p817

  • 自动推断返回值类型, 当没有返回语句, 自动推断为void, 当有一个或多个相同类型的放回语句, 自动推断类型, 但有多个不同类型的返回语句, 需要使用后置返回类型声明返回类型
  • 可以给Lambda指定一个名称
  • Lambda可以访问作用域内的任何动态变量, [v]按值访问v, [&v]按引用访问v, 需要将名称放入[]中, [=]表示可以按访问所有, [&]表示可以按引用访问所有

    int a[]{1, 2, 5, 1, 3, 6};
    sort(a, a+6, 
	    [](const int &a, const int &b)->bool{return a<b; });
    for_each(a, a+6, [](const int &a){cout << a << " "; });
    int big = 10;
    auto toobig = [&big](int a){ return a>big; };

输入输出和文件

C++11

21天速成

最简单的C++21天速成攻略: : 这里写图片描述

litmxs

这个人太懒什么东西都没留下

文章评论(0)