template <class T>
template <typename T> //class 和 typename 区别不大->可适当百度
- 引用的性质
- 可以改变引用的变量的值,但是不能再次成为其它变量的引用。
- 声明引用时,必须同时对其进行初始化。
- 声明引用时,初始化的值不能是NULL。
- 声明引用时,初始化的值可以是纯数值,此时需要使用const关键字修饰引用,表示常引用,其值不可变。
- 可以将变量的引用地址赋值给一个指针,此时指针指向的还是原来的变量。 int& b = a; int* c = &b;
- 可以建立指针的引用。 int* b = &a; int*& c = b;
- 可以用const对引用加以限定(常引用),这样的引用不可以改变引用的值,但是可以改变原变量的值。
- int c(a); // 相当于int c = a;
- 内联inline放在函数定义前
- 函数的默认参数 void show(int a,int b=2,int c=3) //(向后原则)c不可为空了
- 哑元函数 void show(int) //用不到参数
- 类
- MobilePhone(string b,string m,int w):brand(b),model(m),weight(w){}
- private变量可以赋默认值
- 浅拷贝->对于char*拷贝,是地址拷贝,会一起被换
- 深拷贝->需要申请堆空间(new),但是同时记住释放(delete)
- 隐式调用构造函数 Test t2 = s2; 可以使用explicit关键字修饰构造函数来预防
- 名字空间
int a = 1;
// 自定义名字空间
namespace my_space {
int a = 3;
int b = 4;
}
// 使用自定义名字空间
using namespace my_space;
int main()
{
int a = 2;
cout << a << endl; // 2
cout << ::a << endl; // 1
cout << my_space::a << endl; // 3
cout << b << endl; // 4
- void Demo::test() //类体不能不加
- this指针的定义
void test_this()
{
cout << this << endl;
}
- this指针的用法
Teacher(string name) { // 这种重名情况构造初始化列表也可以区分
this->name = name; // 区分成员变量与局部变量
}
- Value& add(int i) // 如果一个类的返回值是当前类的引用,表示该函数支持“链式调用”
{
value += i;
return *this;
}
例:cout << v.add(6).add(-8).add(200).add(34).get_value() << endl; // 332
- 静态成员变量往往都需要初始化,通常需要类内声明,类外初始化。
static int a; //类内
int Test::a = 1; //类外
- 静态成员变量在程序的一开始就开辟了,不需要对象就可以调用,而且更推荐这种调用方式
cout << Test::a << " " << &Test::a << endl; // 1 0x408004
- 静态成员函数可以调用其它静态成员,但是不能调用本类的非静态成员,其原因在于静态成员函数没有this指针,而类中非静态成员的调用都是通过this完成的。非静态的成员函数可以访问静态成员。由于静态成员函数没有this指针,因此相比于非静态的成员函数执行速度有少许的增长。
- 单例模式 就是可能会用到static,用到后就不在创建,用if判断是不是为空
- 常成员函数 /可以调用非const的成员变量,但是不能修改其数值 /不能调用非const的成员函数
- 可以调用非const的成员变量,但是不能修改其数值
- 常量对象 /常量对象中任何成员变量都不能被修改 /常量对象不能调用任何非const的成员函数
- 常成员变量 /编译时可以被修改 /定义时必须初始化
- constexpr常量表达式 // 在编译期就确定了静态成员变量的值
- 友元
- friend void test_friend(Test &t); //参数中含有此类中的成员
- 友元类 friend class B; 可以直接给友元类private成员赋值
/不继承 /单向友元 /不可传递
- 友元成员函数 public: void test_friend(A& a);
- 重载函数
- 友元重载
friend Integer operator +(Integer& i1,Integer& i2); // 双目
friend Integer operator ++(Integer& i); // 单目,前置
friend Integer operator ++(Integer& i,int); // 单目,后置
return ++i.value; //函数直接返回值就ok
- 成员函数运算符重载
Integer operator +(Integer& i); // 双目
Integer operator ++(); // 单目,前置
Integer operator ++(int); // 单目,后置
- 一些特别的运算符重载 //查看4.运算符重载2.4.1
operator string()
{
return str;
} //用法如拷贝
- 赋值运算符重载
Test& Test::operator =(const Test& right)
{
cout << "3" << endl;
str = right.str; //str是类的private成员变量
return *this; //注意
}
- 注意事项
- 重载的运算符必须是C++语言中已有的运算符,即不能创建新的运算符。
- 重载的运算符不能改变其优先级和结合性,也不能改变运算符的操作数个数以及语法结构。
- 运算符重载的参数必须包括用户自定义类型。
- 运算符重载后的功能应该与原有的功能接近或类似,避免没有目的的滥用。
- 运算符重载函数不支持参数的默认值。
- 一般情况下,单目运算符建议重载为成员函数,双目运算符建议重载为友元函数。
- 字符串
- string s4("ABCDEFG",2); // 参数2:从前往后,保留的字符数量
- string s5(s2,2); // 参数2:从前往后,不保留的字符数量
- swap(s5,s6); // 交换两个字符串对象的内容
- s7.append("abc").append("ABC"); //向后追加字符串
- s7.insert(1,"666"); // 参数1:插入的位置 // 参数2:插入的内容
- s7.erase(4,10); // 参数1:抹除的起始位置// 参数2:抹除的字符数量
- sreplace(0,3,"******"); // 参数1:替换的起始位置 // 参数2:替换的原字符数量 // 参数3:替换的新字符内容
- s7.clear(); // 清空
- const char* c = s7.c_str();//【注意】看起来完成了 string → char*,但是这个返回值指向的内容是临时的
strcpy(ca,s7.c_str()); //可以拷贝一下
- 函数模板 //函数模板可以让一个函数的参数或返回值支持不同的数据类型
- template <class T> // T表示通用数据类型
T add(T a,T b)
使用时 cout << add(2,3) << endl; // 5
- 类模板
- template <typename T> // 除了使用class之外,使用typename也可以
class Test //类定义
Test<int> t1(10); //类使用
//注意:类内声明,类外定义时:每个函数定义时,都要写template <typename T>
- 容器
- 顺序容器
- array 数组 //和C中固定大小的数组很相似
array<int,5> arr = {1,2,4}; //注意:“5”这个常量不能是变量,这个空间要在编译时开辟
arr.at(0)ßàarr[0]
arr.fill(521)
- vector 向量 //适合高效地进行随机存取
vector<int> vec(5); //不适合插入和删除
vec.size() vec.at(0)ßàvec[0]
vec.pop_back() // 向后添加元素
vec.insert(vec.begin()+1,222); // 在第二个位置插入元素222
vec.insert(vec.end()-1,4); // 在倒数第二个位置插入元素4
vec.erase(vec.end()-1); // 删除倒数第一个元素
- list 列表 //适合高效地进行插入和删除操作
list<string> lis1(4,"hello"); //不适合使用下标操作元素(只能通过迭代器指针操作元素)
lis1.empty() //也不适合大量的随机存取。
lis1.push_front("qian") // 先前追加
lis1.insert(--lis1.end(),"f68b7s") // 在倒数第二个位置插入元素
list<string>::const_iterator iter = lis1.begin();
advance(iter,4);
- deque 队列 //可以与上面的替换(直接替换)
- 总结:(一些单词,下次累写)
- 关联容器
map<string,int> m;
// 插入元素
m["身高"] = 188;
m["存款"] = 10000;
m["存款"] = 88888; // 如果这个键值对已经存在,则表示修改
m.insert(pair<string,int>("收入",18000));
// 判断一个键是否存在
if(m.find("存款") != m.end())
- 迭代器
- for(string::const_iterator iter = str.begin();iter != str.end();iter++)
string::const_iterator iter
array<string,5>::const_iterator iter
const_iterator iter //只读迭代器
iterator iter //读写迭代器
- 继承
- class Father //基类/父类
class Son:public Father //派生类/子类
- 基类和派生类是相对的,一个基类可以派生出多个派生类,每一个派生类又可以派生出新的派生类。
- 间接继承的类之间也具有继承关系,也可以称之为间接基类和间接派生类。直接继承的类之间称为直接基类和直接派生类。
- 派生类是基类的具体化,而基类是派生类的抽象化。
- 构造函数和析构函数不能被继承。
class Father { Father(int) { cout << "1" << endl; } }; //基类构造
class Son:public Father { public: Son():Father(1){} }; //透传构造
- 委托构造
class Father { Father(int) { cout << "1" << endl; } }; //基类构造
lass Son:public Father { public: Son(int a):Father(a){} }; //委托构造
- 构造函数A可以委托B,B可以委托C......但是,最后那个被委托的构造函数必须透传构造,调用基类的构造函数,不能形成闭环。
- 继承构造
class Son:public Father { public: using Father::Father; }; //继承构造---一句话搞定
直接全部透传---不建议使用
- 对象的创建与销毁流程à
- 多重继承
- class SofaBed:public Sofa,public Bed
SofaBed sb;
sb.lay(); //Sofa类的函数
sb.sit(); //Bed类的函数
- 多重继承的二义性
- 第一种 当两个直接基类出现重名成员时 解决方法:使用作用域限定符
sb.Bed::position(); //
sb.Sofa::position();
- 第二种 菱形继承(钻石继承)
class Sofa:virtual public Furniture
class Bed:virtual public Furniture
- 权限
- 修饰变量和函数
本类中 | 派生类中 | 全局(例如主函数) | |
private | √ | X | X |
protected | √ | √ | X |
public | √ | √ | √ |
class Dad
{
private:
string s1 = "private";
protected:
string s2 = "protected";
public:
string s3 = "public";
void test1()
{
// 在本类中验证三个权限的访问性
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
}
};
class Son:public Dad
{
public:
void test2()
{
// 在派生类中验证三个权限的访问性
// cout << s1 << endl; 错误
cout << s2 << endl;
cout << s3 << endl;
}
};
int main()
{
// 验证在全局三个权限的访问性
// cout << d1.s1 << endl; 错误
// cout << d1.s2 << endl; 错误
cout << d1.s3 << endl;
}
- 公有继承
class Son:public Dad
{
public:
void test1()
{
// cout << s1 << endl; 错误-》不能访问基类私有变量
cout << s2 << endl;
cout << s3 << endl;
}
}; //main函数只能访问共有成员---保护成员也访问不了
- 保护继承
保护继承之后,只有“家族成员”可以查看,之后的继承将
- 私有继承
保护继承之后,之后的继承将访问不到任何成员
- 多态
多态按照字面的意思来讲就是“一种接口,多种状态”,不论传递过来的是哪个类的对象,函数都能够通过同一个接口来适配到各个对象不同的函数调用。
多态实现的前提条件:
- 公有继承
- 函数覆盖
- 基类引用/指针指向派生类对象
- 函数覆盖
class Animal virtual void eat() // 虚函数
class Cat:public Animal void eat() // 虚函数
//简单来说就是把基类的函数内容覆盖了
函数覆盖一定要使用虚函数,虚函数的使用需要注意:
-
-
-
- 只有成员函数、析构函数可以是虚函数
- 如果函数的声明和定义分离,只需要把virtual关键字放在声明处
- 虚函数具有传递性(继承关系、函数名相同、参数列表完全一致、返回值相关)
-
-
在C++11中,新增override关键字以便于验证函数覆盖的有效性。
- 多态的使用
void test_eat1(Animal& a) //有基类和两个以此为基的派生类
{
a.eat();
}
void test_eat2(Animal* a) //普通函数
{
a->eat();
}
//main---函数的内容
Animal a1;
Cat c1;
Dog d1;
test_eat1(a1);
test_eat1(c1);
test_eat1(d1);
Animal* a2 = new Animal;
Cat* c2 = new Cat;
Dog* d2 = new Dog;
test_eat2(a2);
test_eat2(c2);
test_eat2(d2);
- 虚析构函数
-
-
- 如果通过基类引用或指针指向派生类对象,当使用delete销毁对象时,只会调用基类的析构函数,不会调用派生类的构造函数。此时,如果派生类中有new申请的内存资源,那么会导致内训泄漏。
- 解决的方法是把基类的析构函数设置成虚函数,即虚析构函数。
- 除非一个类在设计时已经确定不会有派生类,否则需要把它的析构函数设置虚函数。
-
-
virtual ~Animal() //基类
~Cat() //派生类
Animal* a = new Cat;
delete a; //调用基类析构函数释放资源
- 抽象类
- 抽象类的定义
- 如果基类只表达一些抽象的概念,并不与具体的对象相联系,它可以为派生类提供一个框架,这就是抽象类。
- 如果一个类有至少一个纯虚函数,则这个类是抽象类;
- 如果一个类是抽象类,则这个类至少有一个纯虚函数。
- 纯虚函数是一种特殊虚函数,纯虚函数只有函数声明,没有定义。
- 抽象类的析构函数都应该写为虚析构函数
class Shape
{
public:
// 纯虚函数
virtual void perimeter() = 0;
virtual void acreage() = 0;
virtual ~Shape(){}
}; //如果没有对应的函数定义,是无法创建此类的变量的,且基类无法被创建
- 另外一种情况是,抽象类的派生类只实现了部分纯虚函数。此时派生类仍然是抽象类,其纯虚函数要等待再次派生,直到所有的纯虚函数都在派生类中实现。
class Shape
{
public:
virtual void perimeter() = 0;
virtual void acreage() = 0;
virtual ~Shape(){}
};
class Polygon:public Shape
{
public:
void perimeter()
…
class Rectangle:public Polygon
{
public:
void acreage() // 实现最后一个纯虚函数
…
Polygon pg; //错误,也是抽象类
Rectangle r; //正确
- 异常的定义
- 异常是程序在运行期间产生的问题,即编译期间语法无问题。
- 程序在运行的过程中,一旦出现异常有两种情况:
- 用户有捕获异常的代码,且捕获成功后,可以对当前的异常执行对应的弥补措施的代码。保证程序继续正常执行。
- 程序在抛出异常对象的位置找不倒捕获异常的代码,此时会去调用处再次寻找捕获异常的代码,逐层寻找,如果每一层调用处都没有捕获异常的代码,程序运行终止。
- 处理异常
- 抛出异常
- 程序员也可以手动使用throw关键字抛出异常对象,来干预程序运行的过程。
throw “不能除以0!!!”; //一行代码实现抛
- 程序员也可以手动使用throw关键字抛出异常对象,来干预程序运行的过程。
- 捕获异常
- 捕获异常是针对抛出的异常对象,经过合理的处理,让程序仍然在修复后继续执行。
try
- 捕获异常是针对抛出的异常对象,经过合理的处理,让程序仍然在修复后继续执行。
- 抛出异常
{
cout << division(1,0) << endl;
}catch(const char* e) //如果上面抛出的是数字就用int代替char*
{
cout << e << endl; // 异常对象的信息
cout << "1/0=100,下次不要这么做了" << endl; // 弥补措施
}
-
- 需要注意的是
- try块只能处理一次异常抛出,即尽量不要在try块中放置可能出现两次异常的代码,最理想的try块中应该只包含一句可能出现异常的代码。
- 当代码从try块跳转到catch块进行异常类型匹配时,try块出现异常后的代码不再执行。尽量不要在try块中放置不可能出现异常的代码。
- 如果捕获异常的过程中,抛出的异常类型与捕获异常类型不匹配,程序与没有捕获异常的执行无异。
- 需要注意的是
- 自定义异常类型
- C++中规定了一些标准异常类型,使用的时候需要引入头文件#include <stdexcept>
class ZeroException:public exception //此定义异常类
{
public:
const char* what() const throw()
{
return "不能除以0啊";
}
};
double division(double a,double b) //普通函数
{
if(b==0)
throw ZeroException();
return a/b;
}
try //main里的捕获
{
cout << division(1,0) << endl;
}catch(ZeroException e)
{
cout << e.what() << endl; // 获取异常对象错误信息
}
- 捕获基类异常
- 定义同上,唯有main里的不同
try
- 定义同上,唯有main里的不同
{
string s = "hello";
cout << s.at(-1) << endl;
cout << division(1,0) << endl;
}catch(exception e) // 粗略捕获,标准异常家族的类型都可以捕获
{
cout << e.what() << endl;
}
- 多重捕获
- 类似于switch-case的case块,匹配哪个类型就执行哪个catch块的代码。如果多重捕获的catch块的异常类型之间存在继承关系,应该先捕获派生类异常,再捕获基类异常。
try
- 类似于switch-case的case块,匹配哪个类型就执行哪个catch块的代码。如果多重捕获的catch块的异常类型之间存在继承关系,应该先捕获派生类异常,再捕获基类异常。
{
string s = "hello";
cout << s.at(-1) << endl;
}catch(length_error e) //结合自定义异常处理编写函数
{
cout << "1" << e.what() << endl;
}catch(out_of_range e)
{
cout << "2" << e.what() << endl;
}catch(exception e)
{
cout << "3" << e.what() << endl;
}
- 智能指针 //头文件#include <memory>
- 为什么使用智能指针
C++中堆内存的对象在new之后创建,如果忘记delete则会产生内存泄漏的问题。
使用智能指针,程序员可以使堆内存的对象不需要调用delete就可以自动销毁,达到一种近似于栈内存对象的效果。
- 为什么使用智能指针
智能指针主要用于管理堆内存对象,它将堆内存对象的指针封装为一个栈对象,当外部的栈内存对象生命周期结束后,会在析构函数中释放掉管理的堆内存对象,从而防止内存泄漏。
-
- 智能指针的分类
- auto_ptr
- 自动指针,C++98中引入,目前已经不推荐使用。
- unique_ptr
- 唯一指针,C++11引入
- shared_ptr
- 共享指针,C++11引入
- weak_ptr
- 虚指针,C++11引入
- auto_ptr
- 智能指针的分类
- 智能指针的使用
- auto_ptr的使用
public:
- auto_ptr的使用
Test(string name):name(name)
{
cout << name << "构造函数" << endl;
}
void show()
{
cout << name << "调用成员" << endl;
}
~Test()
{
cout << name << "析构函数" << endl;
}
{
// 新创建的对象A交给ap1管理
auto_ptr<Test> ap1(new Test("A"));
Test* t = ap1.get(); // 获取资源对象
t->show();
// delete t; 错误,被智能指针管理的资源对象不要再拿出来手动销毁了
// 如果要恢复手动管理的方式,请先释放ap1对对象A的所有权
ap1.release();
delete t;
// ap2管理B
auto_ptr<Test> ap2(new Test("B"));
// ap2重新管理C,则B自动销毁
ap2.reset(new Test("C"));
ap2.get()->show();
}
它被淘汰的原因在于其复制语义,当执行拷贝构造函数或赋值运算符操作时,原智能指针对象所持有的堆内存对象的管理权会转移给新的智能指针对象。
{
auto_ptr<Test> ap1(new Test("A"));
auto_ptr<Test> ap2(new Test("B"));
auto_ptr<Test> ap3(new Test("C"));
auto_ptr<Test> ap4(ap1); // 拷贝构造函数
auto_ptr<Test> ap5 = ap2; // 隐式调用
auto_ptr<Test> ap6;
ap6 = ap3; // 赋值运算符
cout << ap1.get() << endl; // 0
cout << ap2.get() << endl; // 0
cout << ap3.get() << endl; // 0
cout << ap4.get() << endl; // 0x1180ff8
cout << ap5.get() << endl; // 0x1181198
cout << ap6.get() << endl; // 0x11811c0
// ap1.get()->show(); 错误
}
-
- unique_ptr的使用
作为对auto_ptr的改进unique_ptr对其持有得堆内存对象具有唯一控制权,即从语法上屏蔽了复制语义。
---->>将上面auto_ptr换成unique_ptr则黑色字体的三行将不能运行,原因是因为unique_ptr把这些操作屏蔽掉了。 - shared_ptr所持有的资源可以在多个shared_ptr之间共享。完美解决上面的问题
shared_ptr除了支持传统的初始化方式外,还支持使用make_shared函数初始化。
- unique_ptr的使用
{
// 使用new来初始化
shared_ptr<Test> sp1(new Test("A"));
// 使用make_shared函数来初始化
shared_ptr<Test> sp2 = make_shared<Test>("B");
sp1.get()->show();
sp2.get()->show();
}
-
-
- 引用计数
每多一个shared_ptr对资源进行管理,资源的引用计数将增加1,每一个管理该资源的shared_ptr对象析构后,资源的引用计数将减少1,当计数减少到0时,释放持有的资源对象。
}
shared_ptr<Test> sp1 = make_shared<Test>("A"); // A构造函数
- 引用计数
-
cout << "引用计数:" << sp1.use_count() << endl; // 1
// 复制语义
shared_ptr<Test> sp2(sp1);
cout << "引用计数:" << sp2.use_count() << endl; // 2
shared_ptr<Test> sp3 = sp2; // 使用sp1也可以
cout << "引用计数:" << sp3.use_count() << endl; // 3
shared_ptr<Test> sp4;
sp4 = sp3; // 使用sp1和sp2也可以
cout << "引用计数:" << sp4.use_count() << endl; // 4
cout << "引用计数:" << sp1.use_count() << endl; // 4
sp1.reset(); // 释放并销毁(引用计数-1)资源
sp2.reset();
sp3.reset();
cout << "引用计数:" << sp4.use_count() << endl; // 1
cout << "引用计数:" << sp1.use_count() << endl; // 0
sp5 = sp4; // 引用计数2
sp4.reset(); // 引用计数为1
}
cout << "引用计数:" << sp5.use_count() << endl; // 1
-
- weak_ptr
weak_ptr是一个不控制资源对象生命周期的智能指针,只是提供了一种对管理对象的访问手段,引入他的目的是协助shared_ptr来工作,weak_ptr也不会影响引用计数。
shared_ptr<Test> sp1 = make_shared<Test>("A");
- weak_ptr
{
// weak_ptr<Test> wp1(new Test("A")); 不能独立使用
// 创建一个weak_ptr对象
weak_ptr<Test> wp1(sp1);
cout << wp1.use_count() << endl; // 1
weak_ptr<Test> wp2 = sp1;
cout << wp2.use_count() << endl; // 1
weak_ptr<Test> wp3 = wp2;
cout << wp3.use_count() << endl; // 1
weak_ptr<Test> wp4;
wp4 = wp1;
cout << wp4.use_count() << endl; // 1
// wp3.get()->show(); 错误
可以把weak_ptr“转换”为shared_ptr后,正常使用,但是要注意此时weak_ptr的引用计数是否因为其它的shared_ptr销毁后置为0。
shared_ptr<Test> sp1 = make_shared<Test>("A");
{
weak_ptr<Test> wp1(sp1);
// 如果此时虚指针对象所持资源没有失效
if(!wp1.expired())
{
// “转换”为share_ptr
shared_ptr<Test> sp2 = wp1.lock();
cout << wp1.use_count() << endl; // 2
shared_ptr<Test> sp3 = wp1.lock();
cout << wp1.use_count() << endl; // 3
cout << sp2.get() << " " << sp3.get() << endl; // 0x1f0ff0 0x1f0ff0
sp2.get()->show(); //4
wp1.lock(); // 未保存返回值,执行完后立刻引用计数-1
cout << wp1.use_count() << endl; // 3
}
- nullptr(掌握)
- C++11中用来代替NULL的
void test(int)
- C++11中用来代替NULL的
{
cout << 1 << endl;
}
void test(char*)
{
cout << 2 << endl;
}
int main()
{
// 源代码中NULL就是0
test(NULL); // 1
test(nullptr); // 2
-
- 类型推导
auto a1 = 1; // auto被推导为int类型
- 类型推导
auto a2 = 1.2; // auto被推导为double类型
auto a3 = new auto(10); // auto被推导为int*
-
- 格式化输出,数字进制输出
// 显示当前的进制:八进制开头0,十六进制开头0x
- 格式化输出,数字进制输出
cout << showbase;
// 切换为八进制
cout << oct;
// 切换为十六进制
cout << hex;
// 切换回十进制
cout << dec;
// 不显示进制
cout << noshowbase;
-
- 域输出
cout << setw(10) << "1" << setw(10) << 1224764646464 << endl;
//不足十位前面补“零”,超过10位全部输出 - 字符串流 //引入头文件#include <sstream>
// int → string
- 域输出
int a = 123;
stringstream ss; //ss的大小有188个字节
ss << a;
string s = ss.str();
cout << s.append("a") << endl;
// string → int
istringstream is(s); //is的大小有184个字节
int i;
is >> i;
cout << i+1 << endl;