C++之前学过一点,但是很长时间都没用过,翻出了书从头看了一遍,简短地做了笔记,以便自己之后查看和学习。这是下篇,上篇链接:C++基础回顾(上)
C++语言中代码复用主要有四种形式:
const int& getMax(const int &a,const int &b){
return a>b ?a:b;
}
const string& getMax(const string &a, const string &b){
return a>b?a:b;
} //两段代码中只有数据类型不同
在类中重载函数时,不同的数据类型需要重新定义,要是能够避开参数类型,使用参数类型的时候填充进去就好了。
函数模板就能够解决上面的问题
//getMax函数模板
template //模板以关键字template开始,紧跟一个模板参数列表
const T& getMax(const T &a, const T &b){
return a>b?a:b;
}
使用上述函数模板时,需要实例化。
模板的参数类型可以自行推断或者自己显式指明
cout<(1.0, 2.5)<
类成员模板
类的成员函数也可以定义为函数模板
class X{
void *m_p =nullptr;
public:
templarte
void reset (T *t){m_p =t;} //成员函数reset定义为一个函数模板
};
可变参函数模板
template //可变数目的参数称为参数包,用省略号“...”表示,可以包含0~任意个模板参数
void foo(Args ...args){
cout<
类模板
// 类模板的定义以关键字template开始,后跟模板参数列表
template //size_t是无符号整数,常用于数组的下标
class Array{
T m_ele[N];
public:
Array(){} //默认构造函数
Array(const std::initializer_list &); //initializer_list是支持有相同类型但数量未知的列表类型,这里没有写形参,应该是一个initializer_list 类型的引用常量
T& operator[](size_t i);
constexpr size_t size() {return N;}
};
//实例化类模板
Arraya;
Arrayb ={1,2,3};
内存在对象创建时分配,在相应的时候比如离开作用域时回收内存。但有时候内存大开小用,根本不需要给对象分配这么多的内存,因此动态内存分配技术派上了用场
动态对象是在动态内存中创建的,动态内存也称为自由存储区或堆。
new用来分配创建动态对象的内存,delete用来释放动态内存。
int *pi =new int; //在堆中创建一个int类型的对象,把他的地址放到指针对象pi中
delete pi; //delete后跟一个指向动态对象的指针,用来释放动态对象的存储空间
pi = nullptr; //上面pi指向的内存已经被释放了,pi是一个空悬指针。通常是在delete之后重置指针指向nullptr
内存泄漏
使用的过程中,一定避免造成无法释放已经不再使用的内存的情况,这种情况也称为内存泄漏
int i,*q =new int(2);
q =&i; //错误:发生内存泄漏
智能指针
用来解决产生空悬指针或内存泄漏等问题
三种智能指针:
1. unique_ptr 独占所指向的对象,采用直接初始化
2. shared_ptr 允许多个指针指向同一个对象,采用直接初始化
3. weak_ptr 一种不控制所指对象生命期的智能指针,指向shared_ptr所管理的对象
三个指针都是类模板,定义在memory头文件中。
{
unique_ptr p2(new int(207)); //类模板,实例化为int类型的,采用直接初始化
}//p2离开作用域被销毁,同时释放其指向的动态内存,也就是说p2消亡时,其指向的对象也会消亡,动态内存自动释放。
shared_ptr p1(new int(100)); //直接初始化
shared_ptr p1=new int(100); //赋值初始化,错误,只能使用直接初始化
动态数组
int n=5;
int *pa =new int[n]; //方括号中必须是整型,不必是常量,返回第一个元素的地址
delete [] pa; //释放内存,逆向销毁,首先销毁最后一个元素
线性链表
线性表在逻辑上和物理结构上都是相邻的。在随机访问数据的时候,可能需要移动很多数据。链式结构则不需要逻辑上相邻的元素在物理结构上也相邻。
线性链表也叫单链表。每个数据元素占用一个结点(node)。一个结点包含一个数据域和一个指针域,其中指针域中存放下一个结点的地址。
单链表利用指针head指向单链表的第一个结点,通过head指针,可以遍历每一个元素,最后一个元素的指针指向为空,尾部的tail指向表尾的结点。
链表中一般有push_back、earse、clear、insert等成员函数。
链栈
栈(stack)只能在一端进行插入和删除操作的线性表。
栈中一般有进栈、出栈、清空、取栈顶元素等操作。
二叉树
一棵非空树有且仅有一个根结点。每个结点的子树的数量为该结点的度(degree)。如结点B的度为3。度为0的结点称为叶子结点,如D、E、H等。
二叉树的定义为每个结点的度不超过2,但是至少要有一个结点的度为2。如下为二叉树的结构。
二叉搜索树,任意一个结点的左子树的数据值都小于该结点的数据值,任意一个结点的右子树的数据值都大于等于该结点的数据值。
继承是代码重用的重要手段之一,可以很容易地定义一个与已有类相似但是又不完全相同的新类。
被继承的称为基类,产生的新类称为派生类。如果一个派生类只有一个基类,称为单继承,如果有多个基类,称为多重继承。
以一个代码例子来讲解其中的原理。
class Person { //基类
protected: //而由protected限定符继承的可以访问其中的受保护成员,但是在派生类外不可访问。
string m_name;//名字
int m_age; //年龄
public:
Person(const string &name = ", int age = 0) :m_name (name), m age(age){}
virtual ~Person()= default; //虚函数,下面有
const string& name ()const{return m name;}
int age()const{ return m_age;}
void plusOneYear(){++m_age;}
void plusOneYear() ++m age; //年龄自增
};
//派生类,需指明基类,形式为" class 派生类名:访问限定符 基类名称{};"
class Student:public Person{ //学生类,公有继承Person 私有的和protected的无法访问,
private:
Course m_course; //课程信息,也是一个类
public:
Student(const string &name, int age, const Course &c):Person(name,age),m_course(c){}
Course course(){return m_course;}
};
派生类对象的构造
Student::Student(const string &name, int age,const course &c):
Person(name,age) /*初始化基类成员*/
m_course(c)/*初始化自有成员*/
{}
多态性包括:编译时多态性和运行时多态性。编译时多态性指的是在程序编译时决定调用哪一个版本的函数,通过函数重载和模板实例化实现。运行时多态是属于一个接口,多种实现。**具体什么意思?**也就是说下面的虚函数,在基类中声明为虚函数是一个接口,在不同的派生类中却有着不同的实现。
虚函数
虚函数表明在不同的派生类中该函数需要不同的实现,因此将该基类中的该函数声明为虚函数。
内联函数、静态成员和模板成员都不能声明为虚函数 。因为这些成员的行为必须在编译时确定,不能实现动态绑定。
输入输出过程中,程序在内存中为每一个数据流开辟一个内存缓冲区。在输入操作过程中,从键盘输入的数据先放在键盘的缓冲区中,按回车键时,键盘缓冲区中的数据流到程序的输入缓冲区,形成cin流,然后用输入运算符>>从输入缓冲区中提取数据并将他们保存到与对象相关联的内存中去。输出操作过程中cout<<首先向控制台窗口输出数据时,先将这些数据送到程序中的输出缓冲区保存,知道缓冲区执行刷新操作,缓冲区中的全部数据送到控制台窗口显示出来。
输出缓冲区刷新的原因:缓冲区满、程序正常结束、遇到endl等。endl、flush、ends等都可以强制刷新缓冲区。
cout<<"endl"<
空白字符(空格符、制表符、回车符等)
这些字符会在输入时被系统过滤掉,想要获取这些这些字符,可以使用cin.get()
for(char c;(c=cin.get())!='\n') //只要不遇到'\n',就能继续输入
cout<
格式化控制
格式化控制可以包括数据的进制、精度和宽度等
//数据进制控制
cout<
浮点数格式控制:打印精度、表示形式、没有小数点部分的浮点值是否打印小数点
默认情况下,浮点数按照6位数字精度打印,并以舍入方式而不是截断方式打印。
可以利用setprecision函数或者IO对象的precision函数来指定打印精度。
//设置打印精度
double x = 1.2152;
cout.precision(3); //此处precision接受整型值3,设置精度为3
cout<<"precision:"<
设置输出格式,科学计数法,定点十进制等
cout <<"default format:"<<10*exp(1.0)<
宽度控制,setw
可以指定输入输出数据占用的宽度,setw接受一个int值,若数据宽度大于设定int值,则按照实际输出,若小于,则采用右对齐,左边补空的方式输出。setfill
则可以指定字符填补空白。
int i=-10;
double x=1.2152;
cout <<"i:"<
文件流
从键盘(cin)和控制台窗口(cout)是使用的IO流对象,如果要从磁盘读取数据或者向磁盘写入数据则需要文件流。
ifstream //从指定文件读取数据
ofstream //向指定文件写入数据
fstream //可以读写数据
ifstream in(ifname); //创建输入文件流对象in,提供文件名ifname初始化
ofstream out; //创建输出文件流对象,没有提供文件名
out.open(ofname); //没提供文件名的话,可以使用open函数关联一个文件。
if (out) //最好检测open操作是否成功
out.close(); //记得关闭文件
ios::in
以读方式打开文件。
ios::out
以写方式打开文件(默认方式)。如果已有此文件,则将其原有内容全部擦除;如果文件不存在,则建立新文件。
ios::app
以写方式打开文件,写人的数据追加到文件末尾。
ios::ate
打开一个已有的文件,并定位到文件末尾。
ios::binary
以二进制方式打开一个文件,如不指定此方式则默认为 ASCII方式。
避免命名冲突,全局作用域分割成为许多子作用域,每个子域为一个命名空间。
namespace Foo {
//放置任何可以放在全局作用域中的声明
}
//命名空间可以是不连续的,在这个文件中命名一点,在另一个文件中再命名一点也可以
//命名空间也可以嵌套
namespace Wang{
namespace Li{
int dosomething(int x,int y);
}
}
//访问时则需要先访问外面的命名空间
int x =Wang::Li::dosomething(1,2);
内联命名空间
内联命名空间可以直接在全局作用域直接访问,而不需要加上命名空间名字
namespace FirstVersion {
void fun(int);
}
inline namespace SecondVersion {
void fun (int);
void fun(double);
}
//调用
FirstVersion::fun(1); //调用早期版本fun函数
fun(1); //调用当前版本fun函数 即second
fun(1.0); //调用当前版本中新增的fun函数,即second
全局命名空间
定义在全局作用域的也就是在全局命名空间的,可以直接使用::
来访问全局命名空间的成员
::memeber_name
程序运行过程中可能会出现错误,为了保证大型程序在运行过程中不会出现错误,C++提供了异常的内部处理机制。包含try、catch、throw三个关键字
throw 抛出异常
try 检测可能会出现异常的代码
catch 捕获异常并处理
try检测异常出现后,系统则检查与try对应关联的catch子句,如果找不到则调用标准库中的函数。
double divide (int a, int b){
if(b==0)
throw "Error,division by zero!"; //抛出异常
return a / b;
}
int a=1,b=0;
try {
int c=divide(a,b); //异常检测
}
catch(const string &str){ //异常处理
cerr <
标准库异常类,需要包含头文件exception
try{
throw MyException(); //抛出MyException类型的异常对象
}
catch(exception &ex){
cerr <
上文中已经提到多重继承,即一个派生类有多种基类,这也很正常,比如一个“学生”首先可以继承“人”这个类,他也可以继承“孩子”这个类,拥有多重身份。蝙蝠可以继承哺乳类,也可以继承飞行类。
多重继承有可能出现二义性问题。二义性而难题被称为死亡钻石问题。
class Animal{protected: //基类Animal
int m_age;
public:
Animal(int n =0):m_age(n){}
virtual void eat(){}
};
class WingedAnimal:public Animal{ //继承Animal
public:
virtual void feedMilk(){}
};
class Mammal: public Animal{ //继承Animal
public:
virtual void flap(){}
};
class Bat:public WingedAnimal,public WingedAnimal{}; //Bat多重继承
Bat b;
b.eat(); //二义性访问,继承的两个类中都继承了Animal中的eat(),该访问哪一个那
通过虚继承可以解决这样的问题
虚继承是将某派生类的基类声明为基类,那么该基类无论被其他人继承多少次,只共享唯一一份的虚基类成员
class WingedAnimal:virtual public Animal{/*...*/};
class Mammal :virtual public Animal[/* ...*/);
C++提供了对日期和时间进行操作的库chrono。该库中提供三个时钟类system_clock steady_clock high_resolution_clock
时钟类名称 | 实时性 |
---|---|
system_clock | 实时时钟 (随着真实世界的时间调整改变,比如夏令时会把标准时间拨早一小时,有吗?) |
steady_clock | 单调时钟(不随外时间调整而改变) |
high_resolution_clock | 实时时钟(精度更高) |
using namespace chrono;
time_t tt = system clock::to_time_t(system_clock::now());
//to_time_t函数将获取的时间点转换成time_t类型
//now获取其数据成员的时间点time_point
cout <
//输出时间间隔
auto start = steady_clock::now();
doSomething(); //执行某种算法
auto end = steady_clock::now();
auto interval = duration_cast (end - start);
cout <
本文的回顾并没有结束,还有一小部分内容没有写出,基本是一些具体函数的使用。
如果您觉得我写的不错,麻烦给我一个免费的赞!如果内容中有错误,也欢迎向我反馈。