第1章 绪论
1.1 C++简史
C++是一种面向对象的语言,实现了继承、抽象、多态和封装等特性,C++支持类,类中包含成员数据和成员方法,成员方法是操作成员数据的函数。C++是一种中级编程语言,既可以编写应用程序,也可以编写驱动程序。
1.2 编写C++应用程序
#include <iostream> //头文件,C++无.h后缀
int main()
{ //单行注释
std::cout << "Hello World!" << std::endl;
return 0;
}
/*
C++文件扩展名为.cpp,编译器在Linux和MAC中常使用g++和clang++,在Windows中常使用MSVC
集成开发环境eclipse(Linux)、xcode(mac)、visualstudio(windows)
使用g++进行编译:g++ -o hello hello.cpp (-std=c++20) 使用C++20特性需加上括号的命令
使用clang++编译:clang++ -o hello hello.cpp (-std=c++20)
*/
1.3 C++输入输出
using namespace std;//声明命名空间std
cout<<"Hello"; //调用std::cout,std命名空间中的cout
std::cout << "Hello World"<<endl;//输出
std::cin >> variable; //输入
1.4 C++20新增功能
1、引入了三向比较运算符(也称为宇宙飞船运算符)
2、模板参数验证和一系列新库,进一步标准化了多线程并通过协程提供同步支持
3、改进了lambda表达式
4、引入模块避免了包含头文件的缺点
第2章 常量和变量(同C语言)
1、命名约定:每个单词首字母大写(帕斯卡命名法)如:MyFirstNumber,第一个单词首字母小写(驼峰命名法)如: myFirstNumber
2、C++增加bool布尔类型,其值为true和false
3、使用auto自动推断类型:auto num = true; //编译器将通过值进行类型推断
4、C++新增:关键字constexpr声明的常量表达式
第3章 数组和字符串(同C语言)
C++增加string类:
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s1("Hello World!"); //字符串初始化
cout << s1 << endl; //字符串输出
string s2;
cin >> s2; //字符串输入
string s3;
s3 = s1 + "_" + s2; //字符串加法
s3 = s1; //字符串赋值
return 0;
}
第4章 表达式与运算符(同C语言)
C++新增:三向比较运算符<=> 同时比较>、<、==三种操作
第5章 控制程序流程(同C语言)
第6章 函数
6.1 带默认值的函数参数
int add(int a, int b = 1)//带默认值的参数必须排在后面,否则无法断定实参的值给哪个形参
{
return a + b;
}
add(1,2) //调用,第2个参数用实参
add(1) //调用,第2个参数用形参默认值
6.2 函数重载
函数名称和返回值类型相同,但参数不同称为函数重载
int add(char a);
int add(int a, int b);
int add(double a, int b);
6.3 引用传递参数
void add(int a, int &b)
{
b = b + a; //函数执行后修改引用参数
}
int n = 1;
add(1, n); //n作为引用
6.4 内联函数
inline void add(int a, int &b);
//该函数将在调用处展开,不进行函数调用
6.5 自动推断函数类型
auto add(int a, int b)
{
return a + b;
}
//编译器根据return语句,推断函数返回值类型
第7章 指针和引用
7.1 动态分配内存
int *q = new int;
int *p = new int[4];
//如果这时有*p++或p++类似的自增自减操作出现,则p的地址被修改,delete将错误!
delete q;
delete[] p;//此处的指针值应是new返回的原值
7.2 引用
int a = 10;
int &b = a;//b声明为引用
void func(int &a)//函数形参引用,可直接对实参进行修改
{
}
引用与const :
int a = 10;
const int &b = a;//const引用,不可修改a的值
//int &c = b; 类型不符,将const引用赋给引用
const int &c = b;
函数参数引用与const
void func(const int &num)//num值不可进行修改
{
}
第8章 类与对象
9.1 基本形式
//声明human类
class human{
private: //私有成员,class默认private
int age; //私有数据,只能类的内部访问和友元访问
public: //公有成员
string name;
void set(int age, string name="wang")//成员函数,通过函数赋值private数据
{ //参数带默认值
this->age = age; //this指针指向对象本身
this->name = name;
}
int get()//成员函数,通过函数获取私有数据的值
{
return age;
}
};
human wang; //实例化对象,创建一个human对象
wang.name = "wang";//访问类成员
wang.set(18); //通过public函数接口设置类数据成员,第2参数使用默认值
int age = wang.get();//获取私有成员的值
human* p = new human;//new分配内存
p->name = "wang"; //访问类成员
wang.set(18, "li");//通过public函数接口设置类数据成员
9.2 构造函数
class human{
private:
int age;
int wealth;
public:
string name;
//接下来对构造函数进行多种函数重载
human() //1、默认构造函数,没有任何构造函数时编译器自动生成
{ //当存在重载的构造函数时编译器不生成默认构造函数
}
human(int age)//2、带参构造函数
{
this->age = age; //当类名与形参名冲突,使用this指针可区分
}
human(int age, int wealth = 998)//3、带默认值参数的构造函数,全部形参带默认值时等同默认构造,错误!
{ // 此时当实参为1个时,情况3与2存在冲突
this->age = age;
this->wealth = wealth;
}
human(int age, int wealth);//4、构造函数可以声明在类中,定义在类外
human(int Age, int Wealth, string Name="wang") :age(Age), wealth(Wealth), name(Name)
{ //5、带初始化列表的构造函数,直接进行初始化
// 此时,参数也可以带默认值
}
};
human::human(int age, int wealth)//在类外实现函数
{
this->age = age;
this->wealth = wealth;
}
9.3 析构函数
class human{
private:
int *p;
public:
human()
{
p = new int[10];//new分配内存
}
~human()
{
delete[] p;
}
};
9.4 拷贝构造函数
拷贝构造函数情况:
1、作为函数参数
2、给新对象初始化赋值
3、函数返回值
class human{
private:
int *p;
public:
human()
{
p = new int[10];//new分配内存
}
human(const human& obj)//拷贝构造函数,对象被复制时编译器调用拷贝构造函数
{
p = new int[10];//new分配内存,然后将obj对象数据复制下来(深拷贝)
}
human& operator= (const human&obj)//=赋值运算重载
{
p = new int[10];//new分配内存,然后将obj对象数据复制下来(深拷贝)
return *this;
}
~human()
{
delete[] p;
}
};
human func(human b) //形参b不执行构造函数,而是直接复制a,执行析构释放同一地址2次,错误!
{
static human a;
return a; //返回对象,此时发生拷贝,错误!
}
human a,b;
b = func(a); //调用拷贝构造,复制对象将它作为参数传递给函数
human c = a; //调用拷贝构造,通过已有对象初始化新建的对象
//c不执行构造函数,而是直接复制a,执行析构释放同一地址2次,错误!
c = a; //调用赋值重载,不是新对象时,复制要 重载=赋值运算符
//加入拷贝构造函数后,复制对象和对象作为函数实参调用时,将调用构造函数进行内存分配
9.5 static数据成员
static用于数据成员时,该成员将在所有实例对象中共享,不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化
class human{
public:
static int age; //静态数据成员,可以是private或public
};
int human::age = 18; //初始化静态数据成员
int main()
{
human::age = 100; //直接使用类静态数据成员
}
9.6 static成员函数
静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)
普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针
void print(string name) //普通函数
{
cout << "print:" << name<<endl;
}
class human{
public:
static int age; //静态数据成员
static void add()
{
++age; //使用静态数据成员
}
static void show()
{
add(); //使用静态成员函数
print("show"); //使用普通函数
cout << "age = " << age << endl;//使用静态数据成员
}
};
9.7 类只有唯一的实例对象
class human{
private:
human(const human&obj); //私有拷贝构造函数
void operator=(const human&obj);//私有赋值运算符
};
class human{
private:
human(){}; //默认构造函数
human(const human&obj); //私有拷贝构造函数
const human& operator=(const human&obj);//私有赋值运算符
string name;
public:
static human& createobj() //返回静态对象,无法使用拷贝构造,因此使用引用方式
{
static human onlyobj; //静态对象,可以使用默认构造函数
return onlyobj;
}
void setname(string name)
{
this->name = name;
}
string getname()
{
return name;
}
};
human& one = human::createobj();//引用唯一静态对象
human two; //1、不可使用默认构造函数
human * three = new human; //2、不可使用默认构造函数
human four = one; //3、不可使用拷贝构造函数
one = human::createobj(); //4、不可使用赋值运算符
human& five = one; //5、可对static唯一的对象,多次引用
9.8 析构函数为private
class human{
private:
~human(){}
public:
static void Destory(human *p)//自定义的静态析构函数
{
delete p;
}
};
human p; //析构函数为private,无法创建对象
human *p = new human; //new对象不会自动销毁,可以创建对象
human::Destory(p); //调用自定义函数
9.9 构造函数进行类型转换
class human{
private:
int age;
public:
explicit human(int num) :age(num)//explicit禁止构造函数进行类型转换
{
}
void show(human h)
{
cout << h.age << endl;
}
};
human h(10); //正常情况
h.show(h);
human g = 11; //禁止类型隐式转换
h.show(9); //禁止类型隐式转换
9.10 友元
友元函数:
class human{
private:
int age;
public:
human(int a=1) :age(a){}
friend void show(const human& p);//声明友元函数
};
void show(const human& p) //定义友元函数
{
cout << p.age << endl;
}
human h(9);
show(h);
友元类:
class human{
private:
int age;
public:
human(int a=1) :age(a){}
friend class student; //声明友元类
};
class student{
public:
void show(const human &p) //可以使用human中的private成员
{
cout << p.age << endl;
}
};
human h(10);
student s;
s.show(h);
9.11 类使用聚合初始化
数组、结构体、共用体、类都可进行聚合初始化
在满足这些条件时,类也可以进行聚合初始化:
1、不包含用户定义的构造函数
2、只包含公有和非静态的数据成员,不包含私有或保护的数据成员
3、不包含任何虚成员函数
4、不涉及继承或只涉及公有继承(不涉及私有继承、保护继承、虚继承)
class human{
public:
int age;
string name;
double dou;
};
human h = { 25, "wang", 3.1415926 };
第9章 继承
基类 | public | protected | private |
public继承 | public | protected | private |
protected继承 | protected | protected | private |
private继承 | private | private | private |
不论什么继承方式,在派生类中 | 可访问 | 可访问 | 不可访问 |
9.1 继承
class human{
private:
int num1;
public:
int num2;
protected:
int num3;
};
//public继承
class student :public human{
//num1不可访问,num2和num3可访问
};
student s;//num1 num3不可访问,num2可访问
//protected 继承
class student :protected human{
//num1不可访问,num2和num3可访问
};
student s;//num1 num2 num3均不可访问
//private 继承
class student :private human{
//num1不可访问,num2和num3可访问
};
student s;//num1 num2 num3均不可访问
9.2 基类与派生类的构造与析构问题
构造顺序: | 基类的数据成员 | 基类的方法 | 子类的数据成员 | 子类的方法 |
虚构顺序: | 子类的方法 | 子类的数据成员 | 基类的方法 | 基类的数据成员 |
9.3 基类初始化
class human{
protected:
int age;
public:
human(int a):age(a){ //基类构造函数
}
};
class student :public human{
public:
student():human(1){ //派生类的构造函数,并调用基类构造函数初始化基类
}
};
9.4 基类与派生类方法名称冲突
class human{
protected:
int age;
public:
human(int a) :age(a){} //构造函数
void show()
{
cout << age << endl;
}
};
class student :public human{
public:
string name;
student(string name, int age=18) :human(age){ //构造函数
this->name= name;
}
void show() //直接覆盖基类的show函数
{
human::show(); //在派生类中调用基类方法
cout << age << endl << name << endl;
}
};
student s("wang",19);
s.show(); //默认调用派生类show
s.human::show(); //通过作用域解析运算符::调用基类show
9.5 多继承
class human{
public:
int age;
void show()
{
cout << "human:" << age << endl;
}
};
class teacher{
public:
int age;
void show()
{
cout << "teacher:" << age << endl;
}
};
class student :public human,public teacher{ //多继承
public:
int age;
void show()
{
cout << "student:" << age << endl;
}
};
student s;
s.age = 100; //默认调用派生类类
s.show();
s.teacher::age = 99; //调用基类
s.teacher::show();
s.human::age = 98; //调用基类
s.human::show();
9.6 final禁止继承
class human final{ //使用final关键字禁止继承
public:
};
class student :public human{ //human无法进行继承,错误!
public:
};
第10章 多态
10.1 虚函数与虚函数表
虚函数:
class human {
public:
void show()
{
cout << "human:基类" << endl;
}
};
class student :public human{
public:
void show()
{
cout << "student:派生类" << endl;
}
};
void func(human& h) //函数形参为基类
{
h.show(); //传入派生类对象,将调用基类成员函数
}
student s;
func(s);
//使用virtual虚函数:传入派生类对象,将调用派生类成员函数
class human {
public:
virtual void show() //将基类函数声明为virtual
{
cout << "human:基类" << endl;
}
};
虚函数表:
class human {
int age;
virtual void show(){} //虚函数
};
class student {
int age;
void show(){}
};
class teacher{
void show();
};
sizeof(human) sizeof(student) sizeof(teacher) //8:4:1
编译器为每个有虚函数的类创建一个虚函数表,虚函数表指针指向该虚函数表
虚函数表:是一个数组,元素为函数指针,指向每个虚函数的实现
实参:派生类对象
形参:基类对象
结果:被函数解读为基类对象,但是虚函数表指针,指向派生类的虚函数表,调用派生类成员函数
10.2 纯虚函数与抽象基类
class human {
public:
virtual void show() = 0; //声明为纯虚函数,于是该类成为抽象基类
};
class student :public human{
public:
void show() //纯虚函数必须实现
{
cout << "class student" << endl;
}
};
void func(human& h)
{
h.show();
}
student s;
func(s);
10.3 虚析构函数问题
class human {
public:
human()
{
cout << "human 构造" << endl;
}
virtual ~human() //声明为虚析构,将同时调用基类与派生类的析构函数
{
cout << "huamn 析构" << endl;
}
};
class student :public human{
public:
student()
{
cout << "student 构造" << endl;
}
~student()
{
cout << "student 析构" << endl;
}
};
void func(human* h) //形参为基类对象
{
delete h; //调用析构函数时,仅调用基类析构
}
student *p = new student; //声明派生类对象
func(p);
10.4 虚继承解决“菱形问题”
class human{
public:
int age;
human()
{
}
};
class man:public human{};
class women :public human{};
class student :public man, public women{};
student s; //此时通过2个父类,创建2个human对象
s.age = 100;//有2个age,错误!
使用虚继承:
//将2个父类声明为虚继承
class man:public virtual human{};
class women :public virtual human{};
10.5 override与final
override明确覆盖意图:
class human{
public:
virtual void print()
{
}
};
class student :public human
{
public:
void print() const //在类成员函数后加const,表示该函数不会修改类的数据成员
{
}
};
void func(human &h)
{
h.print(); //由于派生类加const,基类的虚函数没有覆盖,因此调用基类的函数
}
//加入override
class student :public human
{
public:
void print() const override //明确指出覆盖基类虚函数,
{
}
};
final禁止覆盖:
class human{
public:
virtual void print(){}
};
class student :public human
{
public:
void print() override final{} //override明确覆盖基类print函数,final不允许后续派生类再覆盖
};
class teacher :public student{
public:
void print(){} //此时无法再进行虚函数覆盖,错误!
};
第11章 运算符重载
11.1 基本运算符重载
//1、++与--前缀运算符
int age;
human& operator++()
{
age++;
return *this;
}
//2、++与--后缀运算符
int age;
human operator++(int)
{
human old; //临时对象
old.age = this->age;//复制数据
age++;
return old; //返回旧对象
}
//3、cout<<输出运算符
#include<sstream>
int age;
string str;
operator const char*()
{
ostringstream format;
format << age;
str = format.str();
return str.c_str();
}
//4、加法+与减法-运算符(其它算数运算符类似)
int age;
human operator+(int num)
{
human temp; //临时对象
temp.age = age + num;
return temp;
}
//5、+=与-=运算符(其它复合赋值运算符可类似仿写)
int age;
void operator+=(int num)
{
age += num;
}
//6、==与!=运算符(其它关系运算符可类似仿写)
int age;
bool operator==(const human &p)const
{
return (age == p.age);
}
//7、=赋值运算符
int age;
human& operator=(const human& p)
{
//copy操作
age = p.age;
return *this;
}
//8、[]索引运算符
int arr[10];
int& operator[](const int index)
{
return arr[index];
}
//9、()函数运算符
void operator()(string str)const
{
cout << str << endl;
}
11.2 移动构造与移动赋值
//移动构造函数
int age;
int *p;
human(human&& src)
{
if (src.p != NULL)
{
age = src.age;
p = src.p;
src.p = NULL;//将引用的参数去除,跳过中间复制过程,直接将地址传递
}
}
//移动赋值运算符
human& operator=(human&& src)
{
if (src.p != NULL)
{
age = src.age;
p = src.p;
src.p = NULL;
}
return *this;
}
//对象a+对象b,表达式的值为对象,要返回human
int age;
human operator +(const human& append)
{
human temp; //创建临时变量
temp.age = age + append.age;
return temp;
}
human a, b;
human c(a + b);//(移动构造函数)加法,产生中间对象,再通过拷贝构造初始化对象,
//坏处:深拷贝+中间对象
//改进:直接将中间对象的指针拿过来,清除中间对象,避免深拷贝
human d;
d = a + b + c; //(移动赋值运算符)加法,产生中间对象,进行赋值运算符,对中间对象进行深拷贝,
//做法同上,避免深拷贝操作
11.3 类型转换符
1、static_cast:(可将指针向上转基类,可将指针向下转派生类)
//static_cast执行编译阶段检查,不执行运行阶段检查
class student :public human
human h,*parent;
student s,*son;
parent = &s; //基类指针可以指向派生类对象
son = &h; //派生类指针不可以指向基类对象,错误!
son = static_cast<student*>(&h); //static_cast类型转换
//派生类指针指向基类对象时,使用派生类数据成员将错误
2、dynamic_cast:(运行阶段类型识别)
class human{
virtual void print(){} //基类必须包含多态
};
class student :public human{ //继承human
};
class teacher :public human{ //继承human
};
void func(human* h)
{
student* s = dynamic_cast<student*>(h);//实参student,形参human,再转到student
if (s) //转换失败指针为NULL
cout << "student YES" << endl;
else
cout << "student NO" << endl;
teacher* t = dynamic_cast<teacher*>(h);//实参teacher,形参human,再转到teacher
if (t)
cout << "teacher YES" << endl;
else
cout << "teacher NO" << endl;
}
student s;
teacher t;
func(&s);//结果:student YES teacher NO
func(&t);//结果:student NO teacher YES
//当func函数内使用static_cast进行类型转换时,结果全为YES,因为基类形参可转为派生类
3、reinterpret_cast:(re interpret表示重新解释,可以强行转换类型)
int *p;
string s = "Hello";
p = reinterpret_cast<int*>(&s);
cout << *p << endl;
4、const_cast:(关闭对象的const访问修饰符)
class human{
public:
int age;
human(int a = 0) :age(a){}
};
void func(const human& p)
{
human& h = const_cast<human&>(p); //此处强制关闭const,修改对象的值
h.age = 100;
}
human h;
func(h);
cout << h.age << endl;
5、总结
double pi = 3.1415926;
//1、C++ style
int num = static_cast<int>(pi);
//2、C style
int num = (int)pi;
//3、编译器
int num = pi;
第12章 模板与宏
12.1 宏
1、#define定义常量(同C语言)
2、#define编写宏函数(同C语言)
12.2 模板
1、模板函数:
template <typename T> //声明该函数为模板,template和typename为关键字,T为替换的名称
const T& getmax(const T& a, const T& b) //函数
{
if (a > b)return a;
else return b;
}
int a = 1, b = 2;
dispaly<int>(a, b); //带<int>指明类型为int
//编译器生成函数:const int& getmax(const int& ,const int&)
double x = 1.99, y = 2.01;
dispaly(x, y); //不带时,编译器自动推断,对于模板类必须显式的指明类型
//编译器生成函数:const double& getmax(const double& ,const double&)
2、模板类:
//1、模板类基本语法
template <typename T1=int, typename T2=int>//带默认参数,同函数类似
class human
{
private:
T1 value1;
T2 value2;
public:
human(const T1& va1, const T2& va2):value1(va1), value2(va2){}
void print()
{
cout << value1 << endl << value2 << endl;
}
};
human<const char*,double>p1("Hello", 1.99);//带类型值
human<>p2(6, 1.99); //不带类型值,使用默认值,(但将1.99解释为int)
//2、使用student类作为模板类型参数
class student{
public:
};
student s;
human<int, student>p3(6, s);
//3、对human模板的具体化:(可能在使用不同类型实例化模板时,需要增删不同的成员,因此具体化模板)
template<> class human<int, int>{
private:
int value1;
int value2;
string name; //增加数据成员
public:
human(const int& va1, const int& va2) :value1(va1), value2(va2){}
void print()
{
cout << value1 << endl << value2 << endl;
}
};
//4、模板类静态成员
template <typename T>
class human
{
public:
static T num; //静态成员
};
template<typename T> T human<T>::num; //初始化模板类的静态成员
human <int>a;
a.num = 100; //不同类型具体化的类,静态成员不同
human<double>b;
b.num = 3.1415926;
3、参数数量可变的模板:
template<typename T1, typename T2> //原始模板函数
void func(T1& result, T2& value)
{
result = result + value;
}
//可变参数模板函数
template <typename T_1, typename T_2, typename...T_N>
void func(T_1& result, T_2 value_1, T_N... value_n)//参数1使用引用作为结果
{
result = result + value_1;
return func(result, value_n...); //反复处理参数
}
double result=0;
func(result, 2, 3, 4.2, 5, 6, 7.123);
string name;
func(name, "Hello "," World!");
cout << result << endl << name.c_str() << endl;