C++ 操作符重载
操作符重载是为了运算更加符合我们在数学上的使用习惯。
1. 定义
operator <某个操作符>
2. 原则
1)遵循已有操作符的语法,如单目只能重载成单目
2)遵循已有操作符的语义(但这个不是必须的)
3)操作符重载的实现路径:可以是一个类的非静态成员函数,也可以是带有类、结构、枚举以及它们的引用类型参数的全局函数
4)可重载的操作符
除了下面五个操作符外,其他的操作符都可以重载
"."(成员选择符) , ".*"(间接成员选择符),"::"(域解析符),"?:"(条件操作符),"sizeof"(数据占用内存大小)
在操作符重载时,要注意是否会发生内存泄漏问题!!
C++ 继承方式
1. public
1)基类的public成员,在派生类中成员public成员
2)基类的protected成员,在派生类中成员protected成员
3)基类的private成员,在派生类中成员不可直接使用的成员
2. protected
1)基类的public成员,在派生类中成员protected成员
2)基类的protected成员,在派生类中成员protected成员
3)基类的private成员,在派生类中成员不可直接使用的成员
3. private
1)基类的public成员,在派生类中成员private成员
2)基类的protected成员,在派生类中成员private成员
3)基类的private成员,在派生类中成员不可直接使用的成员
C++ 继承方式的调整
在任何继承方式中,除了基类的private成员外,都可以在派生类中分别调整其访问控制。
调整格式
[public: | protected: | private: ] <基类名>:: <基类成员名>;
class A
{
public:
void f1();
void f2();
void f3();
protected:
void g1();
void g2();
void g3();
}
class B: private A
{
public:
A::f1;//把f1调整为public
A::g1;//把g1调整为public,是否允许弱化基类的访问控制要视具体的实现而定
protected:
A::f2;//把f2调整为protected
A::g2;//把g2调整为protected
}
class C: class B
{
public:
void h()
{
f1(); f2(); g1(); g2();//OK
f3(); g3(); //Error,此时f3,g3是基类B的private成员
}
}
C++ 消息(成员函数调用)的动态绑定
1. 消息的多态性
C++把类看作类型,把以public方式(严格讲:只有public方式)继承的派生类看做基类的子类型,这样就使得在C++面向对象程序中存在下面的三种多态:
1)对象类型的多态:派生类对象的类型既可以是派生类,也可以是基类。
2)对象标志的多态:基类的指针或引用可以指向或引用基类对象,也可以指向或引用派生类对象。(但是派生类的指针只能指向或引用派生类对象,不能指向或引用基类对象,可以理解为可以将多的给少的,但是不能把少的给多的。)
3)消息的多态,一个可以发送到基类对象的消息,也可以发送到派生类对象。
2. 静态绑定
在编译时刻绑定数据的类型。(默认)
3. 动态绑定
在运行的时候绑定数据的类型。
class A
{
int x,y;
public:
void f();
}
class B: public A
{
int z;
public:
void f();
void g();
}
void func1(A& x)
{
x.f();//调用A::f,因为C++是静态绑定
}
void func2(A* p)
{
p->f();//调用A::f,因为C++是静态绑定
}
3. 虚函数与消息的动态绑定
1)虚函数的定义
在函数返回类型前面加上virtual
class A
{...
public:
virtual void f();//虚函数
}
限制:
a. 只有类的成员函数才可以是虚函数
b. 静态成员函数不能是虚函数
c. 构造函数不能是虚函数
d. 析构函数可以是(往往)是虚函数
2)虚函数的动态绑定
基类中的一个成员函数如果被定义成虚函数,则在派生类中定义的与之相同型构的成员函数是对该基类成员函数的重定义(或称覆盖,override)。
相同型构:函数名相同、参数类型和个数相同、返回值类型相同或者是基类成员函数返回值类型的派生类
class A
{
int x;
public:
virtual void f();
}
class B: public A
{
int y;
public:
void f();
void g();
}
A *p = new B;
p->f(); //OK,调用B的f
p->g(); //Error,因为A中没有g
((B*)p)->g(); //OK,调用B的g
C++ 纯虚函数和抽象类
1. 纯虚函数
定义格式:在函数原型后面加上符号 “=0”
class A
{
...
public:
virtual int f()=0;
}
2. 抽象类
包含纯虚函数的类成为抽象类,用于为派生类提供一个基本框架和一个公共的对外接口,派生类(或派生类的派生类...),应对抽象基类的所有纯虚成员函数进行实现。(形式上跟Java的抽象类有点像,作用跟Java的接口有点像)
C++ 多继承
1. 定义格式
class <派生类名>: [<继承方式>] <基类名1>, [<继承方式>] <基类名2>,...
{ <成员说明表>
};
2. 重复继承——虚基类
在多继承中,如果直接基类有公共的基类,则会出现重复继承。这样,公共基类中的数据成员在多继承的派生类中皆有多个拷贝。
如:
class A
{
int x;
};
class B: public A{};
class C: public A{};
class D: public B, public C{};
//此时D用有两个x成员B::x 和 C::x
为了解决这样的问题,要使用虚基类。
1)虚基类的构造函数有最新派生出的类的构造函数调用
2)虚基类的构造函数优先非虚基类的构造函数执行
class A
{
int x;
public:
A(int i) {x=i;}
};
class B: virtual public A
{
int y;
public:
B(int i): A(1) {y=i;}
};
class C: virtual public A
{
int z;
public:
C(int i): A(2) {z=i;}
};
class D: public B, public C
{
int m;
public:
D(int i, int j, int k): B(i),C(j),A(3){m=k;}
};
class E: public C
{
int n;
public:
E(int i, int j, int k, int l): D(i,j,k),A(4){n=1;}
}; D d(1,2,3); E e(1,2,3,4);
当创建D对象d时,所调用的构造函数的顺序是:
A(3), B(1), C(2), D(1,2,3);
当创建E对象e时,所调用的构造函数的顺序是:
A(4), B(1), C(2), D(1,2,3), E(1,2,3,4)
C++ 模板
1. 类属性
类属性: 一个程序实体能对多种类型的数据进行操作或描述的特性。
类属函数:一个函数能对不同类型的数据(参数)完成相同的操作。
类属类:一个类的成员类型可变。
2. 函数模板
动态语言中在定义参数是不用指定类型,所以本身就具有类属性。
C++时一种静态类型语言,可以通过宏定义、指针类型参数、函数模板等方式实现类属性。
3. 指针类型参数
将参数类型定义为void *,因为void *可以接受任何指针类型。
但是这种方式可读性差,并且实现麻烦,一般不用。
4. 函数模板
定义
template <class T1, class T2, ...>
<返回值类型> <函数名> (<参数表>)
{...}
实例化
隐式:编译程序根据调用时实参的类型自动把函数模板实例化为具体的函数。
显式:sort<int>(a,100); sort<double>(b,100);等
也可以是带有具体类型的:
template <class T, int size>
void fun (T a)
{...}
f<int, 10>(a);
5. 类模板
template <class T1, class T2, ...>
class <类名>
{<类成员声明>
};
在类外部定义的成员函数
template <class T1, class T2, ...>
<返回值类型> <类名> <T1, T2, ...>::<成员函数名>(<参数表>){...}
C++ 标准模板库
1. 容器
容器用于存储数据元素,它们是由长度(元素个数)可变的同类型的元素所构成的序列,如向量、集合、栈、队列等。
vector<元素类型>:用于快速定位(访问)任意位置上的元素以及主要在元素序列的尾部增加/删除元素的场合。在vector头文件中定义,用动态数组实现。
map<关键字类型,值类型> 和 multimap<关键字类型,值类型>:元素按关键字排序,multimap中不同元素的关键字可以相同,在map头文件中定义,常常用某种二叉树实现。
set<元素类型>和multiset<元素类型>:在set头文件中定义,set可以用于去重。
basic_string<元素类型>:与vector相似,不同之处在于其元素类型是字符类型,并提供一系列与字符串相关的操作。string和wstring是它的两个实例basic_string<char>和basic_string<wchar_t>。在string头文件中定义。
list<元素类型>:用于经常在元素序列中任意位置上插入/删除元素的场合。在list头文件中定义,用双向链表实现。
deque<元素类型>:用于主要在元素序列的两端增加/删除元素以及需要快速定位(访问)任意位置上的元素的场合。在deque头文件定义,用分段的连续空间结构实现。
stack<元素类型>:用于仅在元素序列的尾部增加/删除元素的场合,在stack头文件中定义,一般基于deque来实现。
queue<元素类型>:用于仅在元素序列的尾部增加,头部删除元素的场合。在queue头文件中定义,一般基于deque来实现。
priority_queue<元素类型>:与queue的操作类似,不同之处在于每次增加元素之后,它将对元素位置进行调整,使得头部元素总是最大的。也就是说,每次删除的总是最大的元素。在queue头文件中定义,一般基于vector和heap结构来实现。
2. 迭代器
迭代器实现了抽象的指针(智能指针),它们指向容器中的元素,用于对容器中的元素进行访问和遍历。
输入迭代器:只能用于读取它所指向的容器元素。
输出迭代器:只能用于修改它所指向的容器元素。
前向迭代器:可以用于读取和修改它所指向的容器元素。
双向迭代器:可以用于读取/修改它所指向的容器元素。
随机访问迭代器:可以用于读取/修改它所指向的容器元素。
3. 算法
调序算法:改变容器中元素的次序。如sort reverse random_shuffle等
编辑算法:用于实现对容器元素的复制、替换、删除、交换、合并、赋值等操作。如copy replace remove unique swap等
查找算法:用于在容器中查找元素或子元素序列。如find count search等
算术算法:用于对容器内的元素进行求和、内积和、差等。如accumulate partial_sum inner_product等
集合算法:用于实现集合的运算。如include set_union set_intersection等
堆算法:用于实现按对结构存储和操作容器中的元素,具有堆结构的容器的主要特点之一是:第一个元素总是最大的。如make_heap pop_heap等
元素遍历并操作算法:依次访问一个范围内的每个元素,并对每个元素调用某个指定的操作函数或函数对象f。for_each
C++ 异常处理机制
void f()
{
...
...throw 1;
...
...throw 1.0;
...
...throw "abcd";
...
}
int main()
{
...
try
{
f();
}
catch(int){}
catch(double){}
catch(char*){}
}