类:具体类型;抽象类型;虚函数;类层次
拷贝和移动:拷贝容器;资源管理;抑制制作
模板:参数化类型;函数模板;函数对象;可变参数模板;别名
C++如何支持抽象和资源管理。支持面向对象编程和泛型编程等编程风格。
类
C++最核心的语言特性就是类。类是一种用户自定义的数据类型。
具体类别
一种算术类型
复数类complex----------标准库中是有的
定义在类内部的函数默认是内联的。
complex.h
class complex{
private:
double re, im; //表现形式,两个双精度浮点数
public:
complex(double r, double i) :re{ r }, im{ i }{}
complex(double r) :re{ r }{}
complex() :re{ 0 }, im{ 0 }{}
double real() const{ return re; } //const说明不会修改对象
void real(double d){ re = d; }
double imag() const{ return im; }
void imag(double d){ im = d; }
complex& operator+=(complex z){ re += z.re, im += z.im; return *this; } //返回的是引用
complex& operator-=(complex z){ re -= z.re, im -= z.im; return *this; }
complex& operator*=(complex z){
double temp1 = re;
double temp2 = im;
re = (temp1*z.re - temp2*z.im),
im = (temp1*z.im + temp2*z.re);
return *this;
}
//complex operator+(complex a, complex b){ return a += b; } ////双目运算符重载声明在类外,否则就会出错
};
complex operator+(complex, complex);
complex operator-(complex, complex);
complex operator-(complex);
complex operator*(complex, complex);
bool operator==(complex, complex);
bool operator!=(complex, complex);
complex.cpp
#include "complex.h"
complex operator+(complex a, complex b){ return a += b; }
complex operator-(complex a, complex b){ return a -= b; }
complex operator-(complex a){ return{ -a.real(), -a.imag() }; }
complex operator*(complex a, complex b){ return a *= b; }
bool operator==(complex a, complex b){
return a.real() == b.real() && a.imag() == b.imag();
}
bool operator!=(complex a, complex b){
return !(a == b);
}
test.cpp
#include <iostream>
#include "complex.h"
using namespace std;
void f(){
complex a(2,1);
complex b(2,3);
complex c(a * b);
cout << c.real() << " " <<c.imag() << '\n';
}
int main(){
f();
}
容器
容器是指一个包含若干元素的对象。
Vector为容器类型,Vector的对象都是容器,Vector作为double的容器。
更精确的资源释放控制----析构函数。
~Vector(){delete[] elem;}
初始化容器
1.初始化器列表构造函数
2.push_back(): 在序列的末尾添加一个新元素
抽象类型
抽象类型将使用者与类的实现细节完全隔离开来。抽象类型分离接口和表现形式并且放弃了纯局部变量。
含有纯虚函数的类称为抽象类。
针对Vector_container类
#include <iostream>
using namespace std;
class Vector{ //具体类
public:
Vector(int s) :elem{ new double[s] }, sz{ s }{} //初始化 使用了成员初始化器列表来初始化成员
double& operator[](int i){ return elem[i]; } //通过下标访问
int size() const{ return sz; }
private:
double* elem; //指向元素的指针
int sz; //元素的个数
};
class Container{ //比Vector更抽象的类,称为抽象类,= 0为纯虚函数,派生类必须定义这个函数
public:
virtual double& operator[](int) = 0; //纯虚函数
virtual int size() const = 0;
virtual ~Container(){} //析构函数
};
//作为一个抽象类,在 Container中没有构造函数,毕竟它没有什么数据需要初始化,析构函数也是虚的,抽象类需要通过引用或指针来操纵,但不清楚它的实现部分到底拥有哪些资源
void use(Container& c){ //use()在完全忽视实现细节的情况下使用了Container的接口 size() [],却根本不知道哪个类型实现了它们
const int sz = c.size(); //如果一个类负责为其他一些类提供接口,将前者称为“多态类型”
for (int i = 0; i != sz; ++i){
cout << c[i] << '\t';
}
}
//一个容器为了实现抽象类Container接口所需的函数,可以使用具体类Vector,派生类
//基类 子类和超类 继承
class Vector_container : public Container{ //Vector_container实现了Container
Vector v; //使用了具体类型Vector
public:
Vector_container(int s) :v(s){} //初始化,含有s个元素的Vector 注意成员v的构造函数被隐式调用
~Vector_container(){};
double& operator[](int i){ return v[i]; } //覆盖 override
int size() const { return v.size(); } //覆盖 override
};
void g(){
Vector_container vc(5);
for (int i = 0; i != vc.size(); ++i)
cin >> vc[i];
use(vc); //因为use()只知道Container的接口而不了解Vector_container,因此对于Container的其他实现,use()仍能正常工作
}
int main(){
g();
}
下面是针对List_container类
class List_container : public Container{ //List_container实现了Container
std::list<double> ld; //一个double类型的标准库list 需要在前面#include <list>
public:
List_container(){}
List_container(initializer_list<double> il) :ld{il}{}
~List_container(){}
double& operator[](int i);
int size() const { return ld.size(); }
};
double& List_container::operator[](int i){
for (auto& x : ld){ //注意x是从头遍历的,所以通过i==0 和 --i配合,可以定位到具体的i位置
if (i == 0) return x;
--i;
}
throw out_of_range("List Container");
}
void h(){
List_container lc = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
use(lc);
}
虚函数
虚函数表 virtual function table, vtbl
类层次
类层次是指通过 派生创建的一组类。接口继承 、 实现继承。
具体类:类似于内置类型,通常将其定义为局部变量。
类层次中的类,倾向于通过new在自由存储中为其分配空间,然后通过指针或引用访问他们。
释放内存很重要 delete unique_ptr.
拷贝和移动
逐成员的复制: complex
但是对于Vector一样的复杂具体类型,逐成员的复制常常是不正确的,抽象类型更是如此。
拷贝容器
拷贝构造函数 和 拷贝赋值运算符。
#include <iostream>
using namespace std;
class Vector{
private:
double* elem; //指向拥有sz个double的数组
int sz;
public:
Vector(int s) :elem{ new double[s] }, sz{ s }{};
~Vector(){ delete[] elem; }
Vector(const Vector& a); //拷贝构造函数
Vector& operator=(const Vector& a); //拷贝赋值运算符
double& operator[](int i){ return elem[i]; }
const double& operator[](int i)const{ return elem[i]; }; //函数末尾加const表示该函数不修改类中的成员变量
int size()const{ return sz; }
};
Vector::Vector(const Vector& a) :elem{ new double[a.sz] }, sz{ a.sz }{
for (int i = 0; i != sz; ++i)
elem[i] = a.elem[i];
}
Vector& Vector::operator=(const Vector& a){
double* p = new double[a.sz];
for (int i = 0; i != a.sz; ++i){
p[i] = a.elem[i];
}
delete[] elem; //删除旧元素
elem = p;
sz = a.sz;
return *this;
}
int main(){
Vector v1(5);
for (int i = 0; i != v1.size(); ++i){
cin >> v1[i];
}
Vector v2 = v1;
v2[0] = 100;
for (int i = 0; i != v2.size(); ++i){
cout << v2[i]<<'\t';
}
cout << '\n';
for (int i = 0; i != v2.size(); ++i){
cout << v1[i] << '\t';
}
}
移动容器
移动构造函数 Vector(Vector&& a)
移动赋值运算符Vector& operator=(Vector&& a)
标准库函数 move。高效率的使用内存。左值 右值。
资源管理
通过定义 构造函数 拷贝操作 移动操作 析构函数 程序员就可以对受控资源(比如容器中的元素)的全生命周期进行管理。
在很多情况下, 使用资源句柄比用指针效果更好,容易实现 强资源安全(strong resource safety),消除 资源泄露。抑制操作
=delete 。
3.4 模板
一个 模板(template)就是一个类或函数,需要一组类型或值对其 进行参数化,使用模板表示 通用的概念,通过指定实参生成 特定类型的类或函数。参数化类型
#include <iostream>
#include <string>
using namespace std;
template<typename T> //前缀template<typename T>指明T是该声明的形参
class Vector{
private:
T* elem; //elem指向含有sz个T类型元素的数组
int sz;
public:
Vector(int s); //构造函数:建立不变式,获取资源
~Vector(){ delete[] elem; } //析构函数,释放资源
T& operator[](int i);
const T& operator[](int i) const;
int size(){ return sz; }
};
template<typename T>
Vector<T>::Vector(int s){
//if (s < 0) throw Negative_size{};
elem = new T[s];
sz = s;
}
//template<typename T>
//const T& Vector<T>::operator[](int i) const{
// if (i < 0 || size() < i)
// throw out_of_range{"Vector::operator[]"};
// return elem[i];
//}
template<typename T>
T& Vector<T>::operator[](int i){
if (i < 0 || size() < i)
throw out_of_range{"Vector::operator[]"};
return elem[i];
}
int main(){
Vector<string> v1(5);
for (int i = 0; i != v1.size(); ++i){
cin >> v1[i];
}
for (int i = 0; i != v1.size(); ++i){
cout << v1[i]<<'\t';
}
}
函数模板
模板能 参数化标准库中的很多 类型和 算法。#include <iostream>
#include <list>
#include <vector>
using namespace std;
template<typename Container, typename Value>
Value sum(const Container& c, Value v){
for (auto x : c){
v += x;
}
return v;
}
void user(){
list<double> d1 = { 1.0, 2.2, 3.3, 4.4, 5.5 };
vector<int> d2 = { 1, 3, 3, 3 };
double sum1 = sum(d1, 0.0);
int sum2 = sum(d2, 0);
cout << "sum1 = " << sum1 << '\n';
cout << "sum2 = " << sum2 << '\n';
}
int main(){
user();
}
这里的sum()可以看做是标准库accumulate()的简化版本。
函数对象
#include <iostream>
#include <list>
#include <vector>
using namespace std;
//函数对象,是对象就会有类
template<typename T>
class Less_than{
const T val; //常量
public:
Less_than(const T& v) :val(v){}; //参数初始化对象
bool operator()(const T& x)const{ return x < val; } //运算符,函数对象的主要点在此!
};
int main(){
//为某些实参类型定义Less_than类型的具体对象
Less_than<int> lti{ 42 };
Less_than<double> ltd{ 2.2 };
//接下来,像调用函数一样调用该对象
bool b1 = lti(21);
bool b2 = ltd(1.0);
cout << b1 << '\t' << b2 << '\n';
}
#include <iostream>
#include <list>
#include <vector>
using namespace std;
//函数对象,是对象就会有类
template<typename T>
class Less_than{
const T val; //常量
public:
Less_than(const T& v) :val(v){}; //参数初始化对象
bool operator()(const T& x)const{ return x < val; } //运算符,函数对象的主要点在此!
};
//int main(){
// //为某些实参类型定义Less_than类型的具体对象
// Less_than<int> lti{ 42 };
// Less_than<double> ltd{ 2.2 };
// //接下来,像调用函数一样调用该对象
// bool b1 = lti(21);
// bool b2 = ltd(1.0);
// cout << b1 << '\t' << b2 << '\n';
//}
template<typename C, typename P>
int count(const C& c, P pred){ //pred就是函数对象,作为函数实参
int cnt = 0;
for (const auto&x : c){
if (pred(x))
++cnt;
}
return cnt;
}
int main(){
list<double> d1 = { 1.0, 2.2, 3.3, 4.4, 5.5 };
Less_than<double> ltd{ 6.6 }; //作为函数的实际参数
cout << count(d1, ltd) << "\n";
}
体会:类+函数, main()函数中代码越少越好,精心地设计类和函数,
Less_than中简单函数内联,效率更高。
函数对象的精妙之处在于它们随身携带着准备与之比较的值。
lambda expression, lambda 表达式。隐式生成函数对象的表示法。
[&](int a){return a<x;}
可变参数模板
variadic template 接受 任意数量任意类型的实参。别名
using size_t = unsigned int;