C++ 基础回顾(下)

C++ 基础回顾(下)

前言

C++之前学过一点,但是很长时间都没用过,翻出了书从头看了一遍,简短地做了笔记,以便自己之后查看和学习。这是下篇,上篇链接:C++基础回顾(上)

C++语言中代码复用主要有四种形式:

  1. 函数,同一个代码模块可以重复调用
  2. 类,同一个类的不同对象公用同一种数据结构和一组操作
  3. 继承和多态,派生类可以重复使用基类代码,多态保证代码复用的灵活性
  4. 模板,泛型编程的基础,不同的实例化版本公用同一份代码设计,是创建类和函数的蓝本

模板和泛型编程

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 <typename T> //模板以关键字template开始,紧跟一个模板参数列表
const T& getMax(const T &a, const T &b){
	return a>b?a:b;
}

使用上述函数模板时,需要实例化。

模板的参数类型可以自行推断或者自己显式指明

cout<<getMax(1.0,2.5)<<endl; //推断参数类型T为double
cout<<getMax<double>(1.0, 2.5)<<endl; //显式指明

类成员模板

类的成员函数也可以定义为函数模板

class X{
	void *m_p =nullptr;
public:
	templarte<typename T>
	void reset (T *t){m_p =t;} //成员函数reset定义为一个函数模板

};

可变参函数模板

template <typename...Args> //可变数目的参数称为参数包,用省略号“...”表示,可以包含0~任意个模板参数
void foo(Args ...args){
	cout<<sizeof...(args)<<endl; //打印参数包args中参数的个数
}
foo(); //0个参数,输出0
foo(1,1.5); //输出2
foo(1,1.5,"C++"); //输出3

类模板

// 类模板的定义以关键字template开始,后跟模板参数列表
template<typename T, size_t N> //size_t是无符号整数,常用于数组的下标
class Array{
	T m_ele[N];
public:
	Array(){} //默认构造函数
	Array(const std::initializer_list <T> &); //initializer_list是支持有相同类型但数量未知的列表类型,这里没有写形参,应该是一个initializer_list 类型的引用常量
	T& operator[](size_t i);
	constexpr size_t size() {return N;}
};
//实例化类模板
Array<char,5>a;
Array<int,5>b ={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 <int> p2(new int(207)); //类模板,实例化为int类型的,采用直接初始化
}//p2离开作用域被销毁,同时释放其指向的动态内存,也就是说p2消亡时,其指向的对象也会消亡,动态内存自动释放。
shared_ptr<int> p1(new int(100)); //直接初始化
shared_ptr<int> 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"<<endl; //输出endl和一个换行,然后刷新缓冲区
cout<<"flush"<<flush; //输出flush(无额外字符),然后刷新缓冲区
cout<<"ends"<<ends; //输出ends和一个空字符('\0'),然后刷新缓冲区
//显然他们三个还是有所不同的

空白字符(空格符、制表符、回车符等)

这些字符会在输入时被系统过滤掉,想要获取这些这些字符,可以使用cin.get()

for(char c;(c=cin.get())!='\n') //只要不遇到'\n',就能继续输入
	cout<<c;
cout<<endl;

getline(cin,s); //该函数可以获取一行字符序列,遇到回车符结束,不包括回车符

格式化控制

格式化控制可以包括数据的进制、精度和宽度等

//数据进制控制
cout<<showbase<<uppercase; //showbase设置数据输出时显示进制信息,uppercase设置16进制以大写输出并且包含前导字符X
cout<<"default:"<<26<<endl; //默认
cout<<"octal:"<<oct<<26<<endl; //8进制
cout<<"decimal"<<dec<<26<<endl; //10进制
cout<<"hex"<<hex<<26<<endl; //16进制
cout<<noshowbase<<nouppercase<<dec; //输出形式恢复到默认

//上述输出的结果:
default:26
octal:032
decimal:26
hex:0X1A

浮点数格式控制:打印精度、表示形式、没有小数点部分的浮点值是否打印小数点

默认情况下,浮点数按照6位数字精度打印,并以舍入方式而不是截断方式打印。

可以利用setprecision函数或者IO对象的precision函数来指定打印精度。

//设置打印精度
double x = 1.2152;
cout.precision(3);  //此处precision接受整型值3,设置精度为3
cout<<"precision:"<<cout.precision()<<",x="<<x<<endl; //此处precision参数为空,返回当前精度
cout<<setprecision(4);
cout<<"precision:"<<cout.precision()<<",x="<<x<<endl;

//输出
precision:3, x=1.22 //四舍五入,不是截断
precision:4, x=1.215

设置输出格式,科学计数法,定点十进制等

cout <<"default format:"<<10*exp(1.0)<<endl;  //默认格式
cout <<"scientific:"<<scientific<<10*exp(1.0) <<endl; //科学计数法
cout <<"fixed decimal:"<<fixed<<10*exp(1.0)<<endl; //定点表示
cout <<"default float:"<<defaultfloat <<10*exp(1.0)<<endl; //恢复到默认状态

//输出
default format:27.1828
scientific:2.718282e+01
fixeddecimal:27182818
default float:27.1828

宽度控制setw可以指定输入输出数据占用的宽度,setw接受一个int值,若数据宽度大于设定int值,则按照实际输出,若小于,则采用右对齐,左边补空的方式输出。setfill则可以指定字符填补空白。

int i=-10;
double x=1.2152;
cout <<"i:"<<setw(10)<<i<<endl;
cout <<"x:"<<setw(10)<<x<<endl;
cout <<setfill('*')<<"x:"<<setw(10)<<x<<endl;

//输出
i:       -10
x:    1.2152
x:****1.2152

文件流

从键盘(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 <<str <<endl;  //cerr为标准错误ostream对象,常用于输出程序错误信息
}
catch(const char *str){  //匹配到这个异常处理,但是为什么匹配到这个不太懂,好像懂了,不对,还是不懂,好像是匹配到throw的异常时char风格的字符串
	cerr <<str<<endl;
}

标准库异常类,需要包含头文件exception

标准异常类的继承关系

try{
	throw MyException(); //抛出MyException类型的异常对象
}
catch(exception &ex){
	cerr <<ex.what() <<endl; //捕获
}

多重继承与虚继承

上文中已经提到多重继承,即一个派生类有多种基类,这也很正常,比如一个“学生”首先可以继承“人”这个类,他也可以继承“孩子”这个类,拥有多重身份。蝙蝠可以继承哺乳类,也可以继承飞行类。

多重继承有可能出现二义性问题。二义性而难题被称为死亡钻石问题。

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 <<put time(gmtime(&tt),"F")<<endl; 
//gmtime函数将time_t类型函数转换为日历时间
//put_time函数将日历函数转换为时间标准输出流

//输出
2017-12-09 20:15:08
//输出时间间隔
auto start = steady_clock::now();
doSomething();  //执行某种算法
auto end = steady_clock::now();
auto interval = duration_cast <milliseconds > (end - start);
cout <<interval.count () <<endl;

本文的回顾并没有结束,还有一小部分内容没有写出,基本是一些具体函数的使用。
如果您觉得我写的不错,麻烦给我一个免费的赞!如果内容中有错误,也欢迎向我反馈。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值