C++ 基础回顾(下)

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  //模板以关键字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)只能在一端进行插入和删除操作的线性表。

C++ 基础回顾(下)_第1张图片

栈中一般有进栈、出栈、清空、取栈顶元素等操作。

二叉树

一棵非空树有且仅有一个根结点。每个结点的子树的数量为该结点的度(degree)。如结点B的度为3。度为0的结点称为叶子结点,如D、E、H等。

C++ 基础回顾(下)_第2张图片

二叉树的定义为每个结点的度不超过2,但是至少要有一个结点的度为2。如下为二叉树的结构。

C++ 基础回顾(下)_第3张图片

二叉搜索树,任意一个结点的左子树的数据值都小于该结点的数据值,任意一个结点的右子树的数据值都大于等于该结点的数据值。

继承与多态

继承

继承是代码重用的重要手段之一,可以很容易地定义一个与已有类相似但是又不完全相同的新类。

被继承的称为基类,产生的新类称为派生类。如果一个派生类只有一个基类,称为单继承,如果有多个基类,称为多重继承。

以一个代码例子来讲解其中的原理。

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

C++ 基础回顾(下)_第4张图片

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 <

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

你可能感兴趣的:(编程基础,c++,数据结构,开发语言,编程基础)