[TOC]
C++是一门面向对象的语言,诞生于1983年左右,其共经历了多次版本迭代,比较著名的有C++98(1.0),C++11(2.0),C++14等。C语言是用大量的函数来处理数据;C++是用大量的成员函数来处理成员数据。不带指针的类和带指针的类的写法是不一样的。
一、转换函数
转换函数(conversion function)是类的一种特殊的成员函数,该函数定义了一个由程序员自定义的把类对象转换成其他某种类型的方法。该函数的声明形如:operator type()
1、将类对象转换为其他类型
假如有一个类Fraction来表示一个数学上的分数,其需要可以转换为double类型
class Fraction{
public:
Fraction(int num,int den=1):m_numerator(num),m_denominator(den){}// 构造函数
// 转换函数
operator double() const{
return (double) (m_numerator/m_denominator);
}
private:
int m_numerator;// 分子
int m_denominator;// 分母
};
调用如下:
Fraction f(3,5);// 创建了一个分数f:3/5
double d=f; // 分数f转换为小数形式,编译器会隐式调用operator double()来转换为0.6
2、将其他类型转换为类对象
现在假设我们执行如下代码:
Fraction f(3, 5);
Fraction d2 = f + 4; // 希望3/5和4/1两个分数相加
如果我们重载操作符+,则应该是:
class Fraction{
public:
Fraction(int num,int den=1):m_numerator(num),m_denominator(den){}// 构造函数
// 操作符+重载
Fraction operator + (const Fraction& f) {
return Fraction(...);// 没有表示完全,省略
}
private:
int m_numerator;// 分子
int m_denominator;// 分母
};
这个操作符重载函数显然必须是两个Fraction对象才能执行,但是式子f + 4
是Fraction对象+int对象,这个时候编译器会查看Fraction对象的构造函数,如果存在一个只有一个int参数的构造函数,就会先将自动调用Fraction(4)来构建一个临时Fraction对象。这种机制是编译器自动将其他类型转换为类对象,依据是存在参数符合的构造函数,这种构造函数就叫做非显式单参构造函数(non-explicit-one-argument constructor)。
简单来讲,编译器在执行语句时,如果需要将某个类型变量转换为一个类对象,就会去查看该类中是否存在只有一个该类型实参的构造函数,所以这种构造函数就是非明确单参构造函数。
3、explicit关键字的作用
C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).
使用explicit修饰的构造函数就不可以被隐式调用,该操作常用来避免一些歧义。
二、智能指针
智能指针(pointer-like classes,像指针的类)。C++的动态内存分配需要new和delete对应,如果忘记delete容易出现内存泄露,所以为了更加安全地使用动态内存,C++引入了智能指针的概念。
智能指针类似普通的C++指针,其重要区别是它负责自动释放所指向的对象。
C++98提供了auto_ptr;C++11舍弃了C++98的auto_ptr,在头文件<memory>
提供了 3 个新的智能指针类型:
std::unique_ptr<T>
:独占资源所有权的指针。std::shared_ptr<T>
:共享资源所有权的指针。std::weak_ptr<T>
:共享资源的观察者,需要和 std::shared_ptr 一起使用,不影响资源的生命周期。
(1)智能指针的原理
智能指针的用法:
// 创建一个A的类指针,通过类指针调用A.method()
shared_ptr<A> pA(new A());
pA->method();
智能指针是一个C++类模板,封装了C++的普通类指针,重载了*
和->
操作符函数:
template<class T>
class shared_ptr{
public:
shared_ptr(T* p):px(p){}// 构造函数
// 操作符重载函数
T& operator*()const{return *px;}
T* operator->()const{return px;}
private:
T* px;// 普通指针
}
智能指针的行为类似常规指针,重要的区别是
(2)C++泛型编程的迭代器
C++的泛型编程中提供了迭代器,其可以看做就是一种特殊的智能指针。下面简单列举一下链表迭代器的开发思路,首先定义一个链表节点的类模板:
template<class T>
struct __list_node{
void * prev;
void * next;
T data;
}
接着定义一个链表的迭代器的类模板:
template<class T,class Ref,class Ptr>
struct __list_iterator{
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;// 给节点指针起一个别名
link_type node;// 节点指针
// 操作符重载
reference operator*()const{return (*node).data};// 返回对象
pointer operator->()const{return &(operator*())};// 返回对象指针
}
相关用法:
list<A>::iterator iter;
*iter;// 获取A
iter->method();// 调用A::method()
//等价于调用(*iter).method();等价于调用(&(*iter))->method();
三、仿函数
仿函数(Functor)又称为函数对象(Function Object),是一个可以行使函数功能的特殊类,即用类来模拟函数的功能。仿函数是C++提供的一个强大工具,经常在STL中用到,例如作为排序函数的排序规则等。
(1)仿函数的作用
仿函数可以更加方便地将函数作为参数进行传递。
假设我们需要为某个算法(可能是个类或者函数的形式),我们想要为其传入一个函数作为参数。例如我们要设计一个函数,需要在一个数组中找到满足某种条件的个数
,显然该函数的参数一个是数组
;另一个参数则是是否满足某种条件
,这个参数应该是一种函数形式,如果传入函数为参数,在C++中有2种方法可以实现这个需求:
(1)传入函数指针
bool isTrue(int num){
return num>10?true:false;
}
int f(vector<int>& arr,bool (*isTrue)(int)){
int count=0;
for(int n:arr){
if(isTrue(n)) count++;
}
return count;
}
这种方法扩展性较差,当函数参数有所变化,则无法兼容旧的代码,因为这个函数指针的参数只能是int,不方便维护。
(2)传入仿函数。
仿函数的使用和普通函数一样,只是其本质是个类,且该类必须重载 operator() 运算符,因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。
class isTrue{
bool operator()(const int num) {
return num>10?true:false;
}
}
int f(vector<int>& arr,isTrue _isTrue){
int count=0;
for(int n:arr){
if(_isTrue(n)) count++;
}
return count;
}
(2)仿函数的优缺点
仿函数的优点:比函数指针执行速度更快,更加灵活
仿函数的缺点:需要单独实现一个类,定义形式较复杂
四、对象模型
C++多态的实现就是通过类的虚函数,子类函数可以覆盖父类函数的虚函数,达到多态的作用。
1、C++如何实现虚函数?
C++为每个具有虚函数的类对象在内存上添加一个隐藏成员,就是虚指针(vptr,也叫虚表指针),它指向一个虚函数表(vtbl),虚函数表就像一个数组,存放了每个虚函数的地址。
静态绑定:调用类对象的普通函数
动态绑定:通过指针调用类对象的虚函数
虚函数
只要一个类中存在一个虚函数,那么该类的对象的内存空间就存在一个对应的虚指针
类的继承
子类的对象有父类的全部变量