什么时候需要重载赋值操作符?
编译器是否提供默认的赋值操作?
编译器为每个类默认重载了赋值操作符
默认的赋值操作符仅完成浅拷贝
当需要进行深拷贝时必须重载赋值操作符
赋值操作符与拷贝构造函数有相同的存在意义
例 1 默认赋值操作符重载
#include
#include
using namespace std;
class Test
{
int* m_pointer;
public:
Test()
{
m_pointer = NULL;
}
Test(int i)
{
m_pointer = new int(i);
}
Test(const Test& obj)
{
m_pointer = new int(*obj.m_pointer);
}
Test& operator = (const Test& obj)
{
if( this != &obj )
{
delete m_pointer;
m_pointer = new int(*obj.m_pointer);
}
return *this;
}
void print()
{
cout << "m_pointer = " << hex << m_pointer << endl;
}
~Test()
{
delete m_pointer;
}
};
int main()
{
Test t1 = 1;
Test t2;
t2 = t1;
t1.print();
t2.print();
return 0;
}
一般性原则
重载赋值操作符,必然需要实现深拷贝
例 2 数组类的优化
#ifndef _INTARRAY_H_
#define _INTARRAY_H_
class IntArray
{
private:
int m_length;
int* m_pointer;
IntArray(int len);
IntArray(const IntArray& obj);
bool construct();
public:
static IntArray* NewInstance(int length);
int length();
bool get(int index, int& value);
bool set(int index ,int value);
int& operator [] (int index);
IntArray& operator = (const IntArray& obj);
IntArray& self();
~IntArray();
};
#endif
#include "IntArray.h"
IntArray::IntArray(int len)
{
m_length = len;
}
bool IntArray::construct()
{
bool ret = true;
m_pointer = new int[m_length];
if( m_pointer )
{
for(int i=0; i0;
}
}
else
{
ret = false;
}
return ret;
}
IntArray* IntArray::NewInstance(int length)
{
IntArray* ret = new IntArray(length);
if( !(ret && ret->construct()) )
{
delete ret;
ret = 0;
}
return ret;
}
int IntArray::length()
{
return m_length;
}
bool IntArray::get(int index, int& value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
value = m_pointer[index];
}
return ret;
}
bool IntArray::set(int index, int value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
m_pointer[index] = value;
}
return ret;
}
int& IntArray::operator [] (int index)
{
return m_pointer[index];
}
IntArray& IntArray::operator = (const IntArray& obj)
{
if( this != &obj )
{
int* pointer = new int[obj.m_length];
if( pointer )
{
for(int i=0; ireturn *this;
}
IntArray& IntArray::self()
{
return *this;
}
IntArray::~IntArray()
{
delete[]m_pointer;
}
#include
#include
#include "IntArray.h"
using namespace std;
int main()
{
IntArray* a = IntArray::NewInstance(5);
IntArray* b = IntArray::NewInstance(10);
if( a && b )
{
IntArray& array = a->self();
IntArray& brray = b->self();
cout << "array.length() = " << array.length() << endl;
cout << "brray.length() = " << brray.length() << endl;
array = brray;
cout << "array.length() = " << array.length() << endl;
cout << "brray.length() = " << brray.length() << endl;
}
delete a;
delete b;
return 0;
}
编译器默认提供的函数
class Test{};
class Test
{
public:
Test();
Test(const Test&);
Test& operator=(const Test&);
~Test();
};
下面的代码输出什么?为什么?
string s=”12345”;
const char *p=s.c_str();
cout<cout<
例 3 字符串问题 1
#include
#include
using namespace std;
int main()
{
string s = "12345";
const char* p = s.c_str();
cout << p << endl;
s.append("abced"); // p 成为了野指针
cout << p << endl;
return 0;
}
例 4 字符串问题 1
#include
#include
using namespace std;
int main()
{
string s = "12345";
const char* p = s.c_str();
cout << s << endl;
s.append("abced"); // p 成为了野指针
cout << s << endl;
return 0;
}
下面的程序输出什么?为什么?
const char *p=”12345”;
string s=””;
s.reserve(10);
for(int i=0;i<5;i++)
{
s[i]=p[i];
}
if(!s.empty())
{
cout<
例 5 字符串问题 2
#include
#include
using namespace std;
int main()
{
const char* p = "12345";
string s = "";
s.reserve(10);
// 不要使用 C 语言中的方式操作 C++ 中的字符串
for(int i=0; i<5; i++)
{
s[i] = p[i];
}
cout << s << endl;
return 0;
}
例 6 字符串问题 2
#include
#include
using namespace std;
int main()
{
const char* p = "12345";
string s = "";
s=p;
// 不要使用 C 语言中的方式操作 C++ 中的字符串
for(int i=0; i<5; i++)
{
s[i] = p[i];
}
cout << s << endl;
return 0;
}
小结:
在需要进行深拷贝的时候必须重载赋值操作符
赋值操作符合拷贝构造函数有同等重要的意义
string 类通过一个数据空间保存字符数据
string 类通过一个成员变量保存当前字符串的长度
c++开发时尽量避开 c 语言中惯用的编程思想
内存泄漏
动态申请堆空间,用完后不归还
c++语言中没有垃圾回收机制
指针无法控制所指堆空间的声明周期
例 1 内存泄漏
#include
#include
using namespace std;
class Test
{
int i;
public:
Test(int i)
{
this->i = i;
}
int value()
{
return i;
}
~Test()
{
}
};
int main()
{
for(int i=0; i<5; i++)
{
Test* p = new Test(i);
cout << p->value() << endl;
}
return 0;
}
我们需要什么
需要一个特殊的指针
指针生命周期结束时主动释放堆空间
一片堆空间最多只能由一个指针标识
杜绝指针运算和指针比较
解决方案
重载指针特征操作符(->和*)
只能通过类的成员函数重载
重载函数不能使用参数
只能定义一个重载函数
例 2 智能指针
#include
#include
using namespace std;
class Test
{
int i;
public:
Test(int i)
{
cout << "Test(int i)" << endl;
this->i = i;
}
int value()
{
return i;
}
~Test()
{
cout << "~Test()" << endl;
}
};
class Pointer
{
Test* mp;
public:
Pointer(Test* p = NULL)
{
mp = p;
}
Pointer(const Pointer& obj)
{
mp = obj.mp;
const_cast(obj).mp = NULL;
}
Pointer& operator = (const Pointer& obj)
{
if( this != &obj )
{
delete mp;
mp = obj.mp;
const_cast(obj).mp = NULL;
}
return *this;
}
Test* operator -> ()
{
return mp;
}
Test& operator * ()
{
return *mp;
}
bool isNull()
{
return (mp == NULL);
}
~Pointer()
{
delete mp;
}
};
int main()
{
Pointer p1 = new Test(0);
cout << p1->value() << endl;
Pointer p2 = p1;
cout << p1.isNull() << endl;
cout << p2->value() << endl;
return 0;
}
智能指针的使用军规
只能用来指向堆空间中的对象或者变量
小结:
指针特征操作符(->和*)可以被重载
重载指针特征符能够使用对象代替指针
智能指针只能用于指向堆空间中的内存
智能指针的意义在于最大程度的避免内存问题
逻辑运算符的原生语义
操作数只有两种值(true 和 false)
逻辑表达式不用完全计算就能确定最终值
最终结果只能是 true 或者 false
例 1 逻辑表达式
#include
#include
using namespace std;
int func(int i)
{
cout << "int func(int i) : i = " << i << endl;
return i;
}
int main()
{
if( func(0) && func(1) )
{
cout << "Result is true!" << endl;
}
else
{
cout << "Result is false!" << endl;
}
cout << endl;
if( func(0) || func(1) )
{
cout << "Result is true!" << endl;
}
else
{
cout << "Result is false!" << endl;
}
return 0;
}
逻辑操作符可以重载吗?
重载逻辑操作符有什么意义?
例 2 重载逻辑操作符
#include
#include
using namespace std;
class Test
{
int mValue;
public:
Test(int v)
{
mValue = v;
}
int value() const
{
return mValue;
}
};
bool operator && (const Test& l, const Test& r)
{
return l.value() && r.value();
}
bool operator || (const Test& l, const Test& r)
{
return l.value() || r.value();
}
Test func(Test i)
{
cout << "Test func(Test i) : i.value() = " << i.value() << endl;
return i;
}
int main()
{
Test t0(0);
Test t1(1);
if( func(t0) && func(t1) )
{
cout << "Result is true!" << endl;
}
else
{
cout << "Result is false!" << endl;
}
cout << endl;
if( func(1) || func(0) )
{
cout << "Result is true!" << endl;
}
else
{
cout << "Result is false!" << endl;
}
return 0;
}
问题的本质分析
1、c++通过函数调用扩展操作符的功能
2、进入函数体前必须完成所有参数的计算
3、函数参数的计算次序是不定的
4、短路法则完全失效
逻辑操作符重载后无法完全实现原生的语义
一些有用的建议:
实际工程开发中避免重载逻辑操作符
通过重载比较操作符代替逻辑操作符重载
直接使用成员函数代替逻辑操作符重载
使用全局函数对逻辑操作符进行重载
小结:
c++从语法生支持逻辑操作符重载
重载后的逻辑操作符不满足短路法则
工程中不要重载逻辑操作符
通过重载比较操作符替换逻辑操作符重载
通过专用成员函数替换逻辑操作符重载
逗号操作符(,)可以构成逗号表达式
逗号表达式用于将多个子表达式连接为一个表达式
逗号表达式的值为最后一个子表达式的值
逗号表达式中的前 N-1 个子表达式可以没有返回值
逗号表达式按照从左向右的顺序计算每个子表达式的值
exp1,exp2,exp3,….,expN
例 1 逗号表达式的示例
#include
#include
using namespace std;
void func(int i)
{
cout << "func() : i = " << i << endl;
}
int main()
{
int a[3][3] = {
(0, 1, 2),
(3, 4, 5),
(6, 7, 8)
};
int i = 0;
int j = 0;
while( i < 5 )
func(i),
i++;
for(i=0; i<3; i++)
{
for(j=0; j<3; j++)
{
cout << a[i][j] << endl;
}
}
(i, j) = 6;
cout << "i = " << i << endl;
cout << "j = " << j << endl;
return 0;
}
在 C++中重载逗号操作符是合法的
使用全局函数对逗号操作符进行重载
重载函数的参数必须有一个类类型
重载函数的返回值类型必须是引用
Class& operator , (const Class& a,const Class& b)
{
return const_cast
#include
#include
using namespace std;
class Test
{
int mValue;
public:
Test(int i)
{
mValue = i;
}
int value()
{
return mValue;
}
};
/*
Test& operator , (const Test& a, const Test& b)
{
return const_cast(b);
}
*/
Test func(Test& i)
{
cout << "func() : i = " << i.value() << endl;
return i;
}
int main()
{
Test t0(0);
Test t1(1);
Test tt = (func(t0), func(t1)); // Test tt = func(t1);
cout << tt.value() << endl; // 1
return 0;
}
问题的本质分析
1、c++通过函数调用扩展操作符的功能
2、进入函数体前必须完成所有参数的计算
3、函数参数的计算次序是不定的
4、重载后无法严格从左向右计算表达式
工程中不要重载逗号操作符!
小结:
逗号表达式从左向右顺序计算每个子表达式的值
逗号表达式的值为最后一个子表达式的值
操作符重载无法完成实现逗号操作符的原生意义
工程开发中不要重载逗号操作符
下面的代码有没有区别?为什么?
i++; //i 的值作为返回值,i 自增 1
++i; //i 自增 1,i 的值作为返回值
例 1 真的有区别吗?
#include
#include
using namespace std;
int main()
{
int i = 0;
i++;
++i;
return 0;
}
意想不到的现实
现代编译器产品会对代码进行优化
优化使得最终的二进制程序更加高效
优化后的二进制程序丢失了 c/c++的原生语义
不可能从编译后的二进制程序还原 c/c++程序
++操作符可以重载吗?
如何区分前置++和后置++?
++操作符可以被重载
全局函数和成员函数均可进行重载
重载前置++操作符不需要额外的参数
重载后置++操作符需要一个 int 类型的占位参数
例 2 ++操作符的重载
#include
#include
using namespace std;
class Test
{
int mValue;
public:
Test(int i)
{
mValue = i;
}
int value()
{
return mValue;
}
Test& operator ++ ()
{
++mValue;
return *this;
}
Test operator ++ (int)
{
Test ret(mValue);
mValue++;
return ret;
}
};
int main()
{
Test t(0);
t++;
++t;
return 0;
}
真正的区别
对于基础类型的变量
前置++的效率与后置++的效率基本相同
根据项目编码规范进行选择
对于类类型的对象
前置++的效率高于后置++
尽量使用前置++操作符提高程序效率
例 3 复数类的进一步完善
#ifndef _COMPLEX_H_
#define _COMPLEX_H_
class Complex
{
double a;
double b;
public:
Complex(double a = 0, double b = 0);
double getA();
double getB();
double getModulus();
Complex operator + (const Complex& c);
Complex operator - (const Complex& c);
Complex operator * (const Complex& c);
Complex operator / (const Complex& c);
bool operator == (const Complex& c);
bool operator != (const Complex& c);
Complex& operator = (const Complex& c);
Complex& operator ++ ();
Complex operator ++ (int);
};
#endif
#include "Complex.h"
#include "math.h"
Complex::Complex(double a, double b)
{
this->a = a;
this->b = b;
}
double Complex::getA()
{
return a;
}
double Complex::getB()
{
return b;
}
double Complex::getModulus()
{
return sqrt(a * a + b * b);
}
Complex Complex::operator + (const Complex& c)
{
double na = a + c.a;
double nb = b + c.b;
Complex ret(na, nb);
return ret;
}
Complex Complex::operator - (const Complex& c)
{
double na = a - c.a;
double nb = b - c.b;
Complex ret(na, nb);
return ret;
}
Complex Complex::operator * (const Complex& c)
{
double na = a * c.a - b * c.b;
double nb = a * c.b + b * c.a;
Complex ret(na, nb);
return ret;
}
Complex Complex::operator / (const Complex& c)
{
double cm = c.a * c.a + c.b * c.b;
double na = (a * c.a + b * c.b) / cm;
double nb = (b * c.a - a * c.b) / cm;
Complex ret(na, nb);
return ret;
}
bool Complex::operator == (const Complex& c)
{
return (a == c.a) && (b == c.b);
}
bool Complex::operator != (const Complex& c)
{
return !(*this == c);
}
Complex& Complex::operator = (const Complex& c)
{
if( this != &c )
{
a = c.a;
b = c.b;
}
return *this;
}
Complex& Complex::operator ++ ()
{
a = a + 1;
b = b + 1;
return *this;
}
Complex Complex::operator ++ (int)
{
Complex ret(a, b);
a = a + 1;
b = b + 1;
return ret;
}
小结:
编译优化使得最终的可执行程序更加高效
前置++操作符和后置++操作符都可以被重载
++操作符的重载必须符合其原生语义
对于基础类型,前置++与后置++的效率几乎相同
对于类类型,前置++效率高于后置++
标准数据类型之间会进行隐式的类型安全转换
例 1 有趣的隐式类型转换
#include
#include
using namespace std;
int main()
{
short s = 'a';
unsigned int ui = 1000;
int i = -2000;
double d = i;
cout << "d = " << d << endl;
cout << "ui = " << ui << endl;
cout << "ui + i = " << ui + i << endl;
if( (ui + i) > 0 )
{
cout << "Positive" << endl;
}
else
{
cout << "Negative" << endl;
}
cout << "sizeof(s + 'b') = " << sizeof(s + 'b') << endl;
return 0;
}
普通类型与类类型之间能否进行类型转换?
类类型之间能否进行类型转换?
例 2 普通类型->类类型
#include
#include
using namespace std;
class Test
{
int mValue;
public:
Test()
{
mValue = 0;
}
explicit Test(int i)
{
mValue = i;
}
Test operator + (const Test& p)
{
Test ret(mValue + p.mValue);
return ret;
}
int value()
{
return mValue;
}
};
int main()
{
Test t;
t = static_cast(5); // t = Test(5);
Test r;
r = t + static_cast(10); // r = t + Test(10);
cout << r.value() << endl;
return 0;
}
构造函数可以定义不同类型的参数
参数满足下列条件时称为转换构造函数
有且仅有一个参数
参数是基本类型
参数是其他类类型
旧式的 c 方式强制类型转换
int i;
Test t;
i=int(1.5);
t=Test(100);
编译器会尽力尝试让源码通过编译
Test t;
t=100;
100 这个立即数默认为 int 类型,怎么可能赋值给 t 对象呢!现在就报错吗?不急,我
看看有没有构造函数!ok,发现 Test 类中定义了 Test(int i),可以进行转换,默认等价于:
t=Test(100);
编译器尽力尝试的结果是隐式类型转换
隐式类型转换
会让程序以意想不到的方式进行工作
是工程中 bug 的重要来源
工程中通过 explicit 关键字杜绝编译器的转换尝试
转换构造函数被 explicit 修饰时只能进行显示转换
转换方式
static_cast(value);
ClassName(value);
(ClassName)value; //不推介
小结:
转换构造函数只有一个参数
转换构造函数的参数类型是其他类型
转换构造函数在类型转换时被调用
隐式类型转换是工程中的 bug 的重要来源
explicit 关键字用于杜绝隐式类型转换
类类型是否能够转换到普通类型?
C++类中可以定义类型转换函数
类型转换函数用于将类对象转换为其他类型
语法规则:
operator Type()
{
Type ret;
return ret;
}
例 1 类型转换函数初探
#include
#include
using namespace std;
class Test
{
int mValue;
public:
Test(int i = 0)
{
mValue = i;
}
int value()
{
return mValue;
}
operator int ()
{
return mValue;
}
};
int main()
{
Test t(100);
int i = t;
cout << "t.value() = " << t.value() << endl;
cout << "i = " << i << endl;
return 0;
}
类型转换函数
与转换构造函数具有同等的地位
使得编译器有能力将对象转换为其他类型
编译器能够隐式的使用类型转换函数
编译器会经理尝试让源码通过
Test t(1);
int i=t;
t 这个对象为 Test 类型,怎么可能初始化 int 类型的变量呢!现在就报错吗?不急,我看看
有没有类型转换函数!ok,发现 Test 类中定义了 operator int(),可以进行转换。
类类型之间的相互转换
类型转换函数 vs 转换构造函数
例 2 类类型之间的转换
#include
#include
using namespace std;
class Test;
class Value
{
public:
Value()
{
}
explicit Value(Test& t)
{
}
};
class Test
{
int mValue;
public:
Test(int i = 0)
{
mValue = i;
}
int value()
{
return mValue;
}
operator Value()
{
Value ret;
cout << "operator Value()" << endl;
return ret;
}
};
int main()
{
Test t(100);
Value v = t;
return 0;
}
无法抑制隐式的类型转换函数调用
类型转换函数可能与转换构造函数冲突
工程中以 Type toType()的公有成员代替类型转换函数
例 3 qttest
#include
#include
int main()
{
QString str = "";
int i = 0;
double d = 0;
short s = 0;
str = "-255";
i = str.toInt();
d = str.toDouble();
s = str.toShort();
qDebug() << "i = " << i << endl;
qDebug() << "d = " << d << endl;
qDebug() << "s = " << s << endl;
return 0;
}
小结:
c++类中可以定义类型转换函数
类型转换函数用于将类对象转换为其他类型
类型转换函数与转换构造函数具有同等的地位
工程中以 Type toType()的公有成员代替类型转换函数
类之间是否存在直接的关联关系?
例 1 组合关系的描述
#include
#include
using namespace std;
class Memory
{
public:
Memory()
{
cout << "Memory()" << endl;
}
~Memory()
{
cout << "~Memory()" << endl;
}
};
class Disk
{
public:
Disk()
{
cout << "Disk()" << endl;
}
~Disk()
{
cout << "~Disk()" << endl;
}
};
class CPU
{
public:
CPU()
{
cout << "CPU()" << endl;
}
~CPU()
{
cout << "~CPU()" << endl;
}
};
class MainBoard
{
public:
MainBoard()
{
cout << "MainBoard()" << endl;
}
~MainBoard()
{
cout << "~MainBoard()" << endl;
}
};
class Computer
{
Memory mMem;
Disk mDisk;
CPU mCPU;
MainBoard mMainBoard;
public:
Computer()
{
cout << "Computer()" << endl;
}
void power()
{
cout << "power()" << endl;
}
void reset()
{
cout << "reset()" << endl;
}
~Computer()
{
cout << "~Computer()" << endl;
}
};
int main()
{
Computer c;
return 0;
}
组合关系的特点
将其他类的对象作为当前类的成员使用
当前类的对象与成员对象的生命期相同
成员对象在用法上与普通对象完全一致
面向对象中的继承指类之间的父子关系
子类拥有父类的所有属性和行为
子类是一种特殊的父类
子类对象可以当作父类对象使用
子类中可以添加父类没有的方法和属性
C++中通过下面的方式描述继承关系
class Parent
{
int mv;
public:
void method(){};
};
class Child:public Parent
{
};
例 2 继承初体验
#include
#include
using namespace std;
class Parent
{
int mv;
public:
Parent()
{
cout << "Parent()" << endl;
mv = 100;
}
void method()
{
cout << "mv = " << mv << endl;
}
};
class Child : public Parent
{
public:
void hello()
{
cout << "I'm Child calss!" << endl;
}
};
int main()
{
Child c;
c.hello();
c.method();
return 0;
}
重要规则:
子类就是一个特殊的父类
子类对象可以直接初始化父类对象
子类对象可以直接赋值给父类对象
继承是 C++中代码复用的重要手段。通过继承,可以获得父类的所有功能,并且在子类中重
写已有功能,或者添加新功能。
例 3 继承的强化练习
#include
#include
using namespace std;
class Memory
{
public:
Memory()
{
cout << "Memory()" << endl;
}
~Memory()
{
cout << "~Memory()" << endl;
}
};
class Disk
{
public:
Disk()
{
cout << "Disk()" << endl;
}
~Disk()
{
cout << "~Disk()" << endl;
}
};
class CPU
{
public:
CPU()
{
cout << "CPU()" << endl;
}
~CPU()
{
cout << "~CPU()" << endl;
}
};
class MainBoard
{
public:
MainBoard()
{
cout << "MainBoard()" << endl;
}
~MainBoard()
{
cout << "~MainBoard()" << endl;
}
};
class Computer
{
Memory mMem;
Disk mDisk;
CPU mCPU;
MainBoard mMainBoard;
public:
Computer()
{
cout << "Computer()" << endl;
}
void power()
{
cout << "power()" << endl;
}
void reset()
{
cout << "reset()" << endl;
}
~Computer()
{
cout << "~Computer()" << endl;
}
};
class HPBook : public Computer
{
string mOS;
public:
HPBook()
{
mOS = "Windows 8";
}
void install(string os)
{
mOS = os;
}
void OS()
{
cout << mOS << endl;
}
};
class MacBook : public Computer
{
public:
void OS()
{
cout << "Mac OS" << endl;
}
};
int main()
{
HPBook hp;
hp.power();
hp.install("Ubuntu 16.04 LTS");
hp.OS();
cout << endl;
MacBook mac;
mac.OS();
return 0;
}
小结:
继承是面向对象中类之间的一种关系
子类拥有父类的所有属性和行为
子类对象可以当作父类对象使用
子类可以添加父类没有的方法和属性
继承是面向对象中代码复用的重要手段
第 44 课 继承中的访问级别
子类是否可以直接访问父类的私有成员?
根据面向对象理论:
子类拥有父类的一切属性和行为->子类能够直接访问父类的私有成员! ?
根据 c++语法:
外界不能直接访问类的 private 成员->子类不能直接访问父类的私有成员! ?
例 1 继承中的访问级别
#include
#include
using namespace std;
class Parent
{
private:
int mv;
public:
Parent()
{
mv = 100;
}
int value()
{
return mv;
}
};
class Child : public Parent
{
public:
int addValue(int v)
{
mv = mv + v; // ???? 如何访问父类的非公有成员
}
};
int main()
{
return 0;
}
面向对象中的访问级别不只是 public 和 private
可以定义 protected 访问级别
关键字 protected 的意义
修饰的成员不能被外界直接访问
修饰的成员可以被子类直接访问
例 2 protected 初体验
#include
#include
using namespace std;
class Parent
{
protected:
int mv;
public:
Parent()
{
mv = 100;
}
int value()
{
return mv;
}
};
class Child : public Parent
{
public:
int addValue(int v)
{
mv = mv + v;
}
};
int main()
{
Parent p;
cout << "p.mv = " << p.value() << endl;
// p.mv = 1000; // error
Child c;
cout << "c.mv = " << c.value() << endl;
c.addValue(50);
cout << "c.mv = " << c.value() << endl;
// c.mv = 10000; // error
return 0;
}
为什么面向对象中需要 protected?
例 3 综合实例
#include
#include
#include
using namespace std;
class Object
{
protected:
string mName;
string mInfo;
public:
Object()
{
mName = "Object";
mInfo = "";
}
string name()
{
return mName;
}
string info()
{
return mInfo;
}
};
class Point : public Object
{
private:
int mX;
int mY;
public:
Point(int x = 0, int y = 0)
{
ostringstream s;
mX = x;
mY = y;
mName = "Point";
s << "P(" << mX << ", " << mY << ")";
mInfo = s.str();
}
int x()
{
return mX;
}
int y()
{
return mY;
}
};
class Line : public Object
{
private:
Point mP1;
Point mP2;
public:
Line(Point p1, Point p2)
{
ostringstream s;
mP1 = p1;
mP2 = p2;
mName = "Line";
s << "Line from " << mP1.info() << " to " << mP2.info();
mInfo = s.str();
}
Point begin()
{
return mP1;
}
Point end()
{
return mP2;
}
};
int main()
{
Object o;
Point p(1, 2);
Point pn(5, 6);
Line l(p, pn);
cout << o.name() << endl;
cout << o.info() << endl;
cout << endl;
cout << p.name() << endl;
cout << p.info() << endl;
cout << endl;
cout << l.name() << endl;
cout << l.info() << endl;
return 0;
}
小结:
面向对象中的访问级别不只是 public 和 private
protected 修饰的成员不能被外界所访问
protected 使得子类 能够访问父类的成员
protected 关键字是为了继承而专门设计的
没有 protected 就无法完成真正意义上的代码复用
冒泡(:)表示继承关系,Parent 表示被继承的类,public 的意义是什么?
class Parent
{
};
class Child:public Parent
{
};
是否可以将继承语句中的 public 换成 protected 或者 private?如果可以。与 public 继承有什
么关系?
例 1 有趣的尝试
#include
#include
using namespace std;
class Parent
{
};
class Child_A : public Parent
{
};
class Child_B : protected Parent
{
};
class Child_C : private Parent
{
};
int main()
{
return 0;
}
C++中支持三种不同的继承方式
public 继承
父类成员在子类中保持原有访问级别
private 继承
父类成员在子类中变为私有成员
protected 继承
父类中的公有成员变为保护成员,其他成员保持不变
继承成员的访问属性=Max{继承方式,父类成员访问属性}
C++中的默认继承方式为 private
例 2 继承与访问级别深度实践
#include
#include
using namespace std;
class Parent
{
protected:
int m_a;
protected:
int m_b;
public:
int m_c;
void set(int a, int b, int c)
{
m_a = a;
m_b = b;
m_c = c;
}
};
class Child_A : public Parent
{
public:
void print()
{
cout << "m_a" << m_a << endl;
cout << "m_b" << m_b << endl;
cout << "m_c" << m_c << endl;
}
};
class Child_B : protected Parent
{
public:
void print()
{
cout << "m_a" << m_a << endl;
cout << "m_b" << m_b << endl;
cout << "m_c" << m_c << endl;
}
};
class Child_C : private Parent
{
public:
void print()
{
cout << "m_a" << m_a << endl;
cout << "m_b" << m_b << endl;
cout << "m_c" << m_c << endl;
}
};
int main()
{
Child_A a;
Child_B b;
Child_C c;
a.m_c = 100;
// b.m_c = 100; // Child_B 保护继承自 Parent, 所以所有的 public 成员全部变成了 protected 成员, 因此外界无法访问
// c.m_c = 100; // Child_C 私有继承自 Parent, 所以所有的成员全部变成了 private 成员, 因此外界无法访问
a.set(1, 1, 1);
// b.set(2, 2, 2);
// c.set(3, 3, 3);
a.print();
b.print();
c.print();
return 0;
}
一般而言,c++工程项目中只使用 public 继承
c++的派生语言只支持一种继承方式(public 继承)
protected 和 private 继承带来的复杂性远大于实用性
例 3 c++派生语言初探
//C# Demo
class Obj
{
protected string mName;
protected string mInfo;
public Obj()
{
mName = "Object";
mInfo = "";
}
public string name()
{
return mName;
}
public string info()
{
return mInfo;
}
}
class Point : Obj
{
private int mX;
private int mY;
public Point(int x, int y)
{
mX = x;
mY = y;
mName = "Point";
mInfo = "P(" + mX + ", " + mY + ")";
}
public int x()
{
return mX;
}
public int y()
{
return mY;
}
}
class Program
{
public static void Main(string[] args)
{
System.Console.WriteLine("C# Demo"); // C# Demo
Point p = new Point(1, 2);
System.Console.WriteLine(p.name()); // Point
System.Console.WriteLine(p.info()); // P(1, 2)
}
}
//java Demo
class Obj
{
protected String mName;
protected String mInfo;
public Obj()
{
mName = "Object";
mInfo = "";
}
public String name()
{
return mName;
}
public String info()
{
return mInfo;
}
}
class Point extends Obj
{
private int mX;
private int mY;
public Point(int x, int y)
{
mX = x;
mY = y;
mName = "Point";
mInfo = "P(" + mX + ", " + mY + ")";
}
public int x()
{
return mX;
}
public int y()
{
return mY;
}
}
class Program {
public static void main(String[] args){
System.out.println("Java Demo"); // Java Demo
Point p = new Point(1, 2);
System.out.println(p.name()); // Point
System.out.println(p.info()); // P(1, 2)
}
}
小结
C++中支持 3 中不同的继承方式
继承方式直接影响父类成员在子类中的访问属性
一般而言,工程中只使用 public 的继承方式
C++的派生语言中只支持 public 继承方式
如何初始化父类成员?
父类构造函数和子类构造函数有什么关系?
子类中可以定义构造函数
子类构造函数
必须对继承而来的成员进行初始化
直接通过初始化列表或者赋值的方式进行初始
调用父类构造函数进行初始化
父类构造函数在子类中的调用方式
默认调用
适用于无参构造函数和使用默认参数的构造函数
显示调用
通过初始化列表进行调用
适用于所有父类构造函数
父类构造函数的调用
class Child:public Parent
{
public:
Child() //隐式调用
{
cout<<”Child()”<
#include
#include
using namespace std;
class Parent
{
public:
Parent()
{
cout << "Parent()" << endl;
}
Parent(string s)
{
cout << "Parent(string s) : " << s << endl;
}
};
class Child : public Parent
{
public:
Child()
{
cout << "Child()" << endl;
}
Child(string s) : Parent(s)
{
cout << "Child(string s) : " << s << endl;
}
};
int main()
{
Child c;
Child cc("cc");
return 0;
}
构造规则
子类对象在创建时会首先调用父类的构造函数
先执行父类构造函数再执行子类的构造函数
父类构造函数被隐式调用或者显示调用
对象创建时构造函数的调用顺序
1、调用父类的构造函数
2、调用成员变量的构造函数
3、调用类自身的构造函数
例 2 子类构造深度解析
#include
#include
using namespace std;
class Object
{
public:
Object(string s)
{
cout << "Object(string s) : " << s << endl;
}
};
class Parent : public Object
{
public:
Parent() : Object("Default")
{
cout << "Parent()" << endl;
}
Parent(string s) : Object(s)
{
cout << "Parent(string s) : " << s << endl;
}
};
class Child : public Parent
{
Object mO1;
Object mO2;
public:
Child() : mO1("Default 1"), mO2("Default 2")
{
cout << "Child()" << endl;
}
Child(string s) : Parent(s), mO1(s + " 1"), mO2(s + " 2")
{
cout << "Child(string s) : " << s << endl;
}
};
int main()
{
Child cc("cc");
return 0;
}
析构函数的调用顺序构造函数相反
1、执行自身的析构函数
2、执行成员变量的析构函数
3、执行父类的析构函数
例 3 对象的析构
#include
#include
using namespace std;
class Object
{
string ms;
public:
Object(string s)
{
cout << "Object(string s) : " << s << endl;
ms = s;
}
~Object()
{
cout << "~Object() : " << ms << endl;
}
};
class Parent : public Object
{
string ms;
public:
Parent() : Object("Default")
{
cout << "Parent()" << endl;
ms = "Default";
}
Parent(string s) : Object(s)
{
cout << "Parent(string s) : " << s << endl;
ms = s;
}
~Parent()
{
cout << "~Parent() : " << ms << endl;
}
};
class Child : public Parent
{
Object mO1;
Object mO2;
string ms;
public:
Child() : mO1("Default 1"), mO2("Default 2")
{
cout << "Child()" << endl;
ms = "Default";
}
Child(string s) : Parent(s), mO1(s + " 1"), mO2(s + " 2")
{
cout << "Child(string s) : " << s << endl;
ms = s;
}
~Child()
{
cout << "~Child() " << ms << endl;
}
};
int main()
{
Child cc("cc");
cout << endl;
return 0;
}
小结:
子类对象在创建时需要调用父类构造函数进行初始化
先执行父类构造函数然后执行成员的构造函数
父类构造函数显示调用需要在初始化列表中进行
子类对象在销毁时需要调用父类析构函数进行清理
析构顺序与构造顺序对称相反
子类中是否可以定义父类中的同名成员?
如果可以,如何区分?如果不可以,为什么?
例 1 同名成员变量
#include
#include
using namespace std;
class Parent
{
public:
int mi;
};
class Child : public Parent
{
public:
int mi;
};
int main()
{
Child c;
c.mi = 100; // mi 究竟是子类自定义的,还是从父类继承得到的?
return 0;
}
子类可以定义父类中的同名成员
子类中的成员将隐藏父类中的同名成员
父类中的同名成员依然存在于子类中
通过作用域分辨符(::)访问父类中同名成员
访问父类中的同名成员
Child c;
c.mi=100; //子类中的 mi
c.Parent::mi=1000; //父类中的 mi
例 2 同名成员变量深度分析
#include
#include
using namespace std;
namespace A
{
int g_i = 0;
}
namespace B
{
int g_i = 1;
}
class Parent
{
public:
int mi;
Parent()
{
cout << "Parent() : " << "&mi = " << &mi << endl;
}
};
class Child : public Parent
{
public:
int mi;
Child()
{
cout << "Child() : " << "&mi = " << &mi << endl;
}
};
int main()
{
Child c;
c.mi = 100;
c.Parent::mi = 1000;
cout << "&c.mi = " << &c.mi << endl;
cout << "c.mi = " << c.mi << endl;
cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl;
return 0;
}
类中的成员函数可以进行重载
1、重载函数的本质为多个不同的函数
2、函数名和参数列表是唯一的标识
3、函数重载必须发生在同一个作用域中
子类中定义的函数是否能重载父类中的同名函数?
例 3 父子间的函数重载
#include
#include
using namespace std;
class Parent
{
public:
int mi;
void add(int v)
{
mi += v;
}
void add(int a, int b)
{
mi += (a + b);
}
};
class Child : public Parent
{
public:
int mi;
void add(int v)
{
mi += v;
}
void add(int a, int b)
{
mi += (a + b);
}
void add(int x, int y, int z)
{
mi += (x + y + z);
}
};
int main()
{
Child c;
c.mi = 100;
c.Parent::mi = 1000;
cout << "c.mi = " << c.mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl;
c.add(1);
c.add(2, 3);
c.add(4, 5, 6);
cout << "c.mi = " << c.mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl;
return 0;
}
子类中的函数将隐藏父类的同名函数
子类无法重载父类中的成员函数
使用作用域分辨符访问父类中的同名函数
子类可以定义父类中完全相同的成员函数
小结:
子类可以定义父类中的同名成员
子类中的成员将隐藏父类中的同名成员
子类和父类中的函数不能构成重载关系
子类可以定义父类中完全相同的成员函数
使用作用域分辨符访问父类中的同名成员
子类对象可以当作父类对象使用(兼容性)
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
例 1 子类对象兼容性
#include
#include
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
}
void add(int a, int b)
{
mi += (a + b);
}
};
class Child : public Parent
{
public:
int mv;
void add(int x, int y, int z)
{
mv += (x + y + z);
}
};
int main()
{
Parent p;
Child c;
p = c;
Parent p1(c);
Parent& rp = c;
Parent* pp = &c;
rp.mi = 100;
rp.add(5); // 没有发生同名覆盖?
rp.add(10, 10); // 没有发生同名覆盖?
/* 为什么编译不过? */
// pp->mv = 1000;
// pp->add(1, 10, 100);
return 0;
}
当使用父类指针(引用)指向子类对象时
子类对象退化为父类对象
只能访问父类中定义的成员
可以直接访问被子类覆盖的同名成员
子类中可以重定义父类中已经存在的成员函数
这种重定义发生在继承中,叫做函数重写
函数重写是同名覆盖的一种特殊情况
class Parent
{
public:
void print()
{
cout<<”I’m Parent”<
#include
#include
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
}
void add(int a, int b)
{
mi += (a + b);
}
void print()
{
cout << "I'm Parent." << endl;
}
};
class Child : public Parent
{
public:
int mv;
void add(int x, int y, int z)
{
mv += (x + y + z);
}
void print()
{
cout << "I'm Child." << endl;
}
};
void how_to_print(Parent* p)
{
p->print();
}
int main()
{
Parent p;
Child c;
how_to_print(&p); // Expected to print: I'm Parent.
how_to_print(&c); // Expected to print: I'm Child.
return 0;
}
问题分析:
编译期间,编译器只能根据指针的类型判断所指向的对象
根据赋值内容兼容:编译器认为父类指针指向的是父类对象
因此,编译结果只可能是调用父类中定义的同名函数
void how_to_print(Parent *p)
{
p->print();
}
在编译这个函数的时候,编译器不可能知道指针 p 究竟指向了什么。但是编译器没有理由报
错。于是,编译器认为最安全的做法是调用父类的 print 函数,因为父类和子类肯定都有相
同的 print 函数。
编译器的处理方法是合理的吗?是期望的吗?
小结:
子类对象可以当作父类对象使用(兼容性)
父类指针可以正确的指向子类对象
父类引用可以正确的代表子类对象
子类中可以重写父类中的成员函数
父类中被重写的函数依然会继承给子类
子类中重写的函数将覆盖父类中的函数
通过作用域(::)可以访问到父类中的函数
child c;
parent *p=&c;
c.parent::print(); //从父类中继承
c.print(); //在子类中重写
p->print(); //父类中定义
面向对象中期望的行为
根据实际的对象类型判断如何调用重写函数
父类指针(或引用)指向
父类对象则调用父类中定义的函数
子类对象则调用子类中定义的重写函数
面向对象中的多态的概念
根据实际的对象类型决定函数调用的具体目标
同样的调用语句在实际运行时有多种不同的表现形态
C++语言直接支持多态的概念
通过使用 virtual 关键字对多态进行支持
被 virtual 声明的函数被重写后具有多态特性
被 virtual 声明的函数叫做虚函数
例 1 多态的初体验
#include
#include
using namespace std;
class Parent
{
public:
virtual void print()
{
cout << "I'm Parent." << endl;
}
};
class Child : public Parent
{
public:
void print()
{
cout << "I'm Child." << endl;
}
};
void how_to_print(Parent* p)
{
p->print(); // 展现多态的行为
}
int main()
{
Parent p;
Child c;
how_to_print(&p); // Expected to print: I'm Parent.
how_to_print(&c); // Expected to print: I'm Child.
return 0;
}
多态的意义
在程序运行过程中展现出动态的特性
函数重写必须多态实现,否则没有意义
多态是面向对象组件程序设计的基础特性
理论中的概念
静态联翩
在程序的编译期间就能确定具体的函数调用
如:函数重载
动态联翩
在程序实际运行后才能确定具体的函数调用
如:函数重写
例 2 动态联翩与静态联翩
#include
#include
using namespace std;
class Parent
{
public:
virtual void func()
{
cout << "void func()" << endl;
}
virtual void func(int i)
{
cout << "void func(int i) : " << i << endl;
}
virtual void func(int i, int j)
{
cout << "void func(int i, int j) : " << "(" << i << ", " << j << ")" << endl;
}
};
class Child : public Parent
{
public:
void func(int i, int j)
{
cout << "void func(int i, int j) : " << i + j << endl;
}
void func(int i, int j, int k)
{
cout << "void func(int i, int j, int k) : " << i + j + k << endl;
}
};
void run(Parent* p)
{
p->func(1, 2); // 展现多态的特性
// 动态联编
}
int main()
{
Parent p;
p.func(); // 静态联编
p.func(1); // 静态联编
p.func(1, 2); // 静态联编
cout << endl;
Child c;
c.func(1, 2); // 静态联编
cout << endl;
run(&p);
run(&c);
return 0;
}
例 3 江湖恩怨
#include
#include
using namespace std;
class Boss
{
public:
int fight()
{
int ret = 10;
cout << "Boss::fight() : " << ret << endl;
return ret;
}
};
class Master
{
public:
virtual int eightSwordKill()
{
int ret = 8;
cout << "Master::eightSwordKill() : " << ret << endl;
return ret;
}
};
class NewMaster : public Master
{
public:
int eightSwordKill()
{
int ret = Master::eightSwordKill() * 2;
cout << "NewMaster::eightSwordKill() : " << ret << endl;
return ret;
}
};
void field_pk(Master* master, Boss* boss)
{
int k = master->eightSwordKill();
int b = boss->fight();
if( k < b )
{
cout << "Master is killed..." << endl;
}
else
{
cout << "Boss is killed..." << endl;
}
}
int main()
{
Master master;
Boss boss;
cout << "Master vs Boss" << endl;
field_pk(&master, &boss);
cout << "NewMaster vs Boss" << endl;
NewMaster newMaster;
field_pk(&newMaster, &boss);
return 0;
}
小结:
函数重写只可能发生在父类与子类之间
根据实际对象的类型确定调用的具体函数
virtual 关键字是 c++中支持多态的唯一方式
被重写的虚函数可表现出多态的特性
class 是一种特殊的 struct
在内存中 class 依旧可以看作变量的集合
class 与 struct 遵循相同的内存对齐规则
class 中的成员函数与成员变量时分开存放的
每个对象有独立的成员变量
所有对象共享类的成员函数
值得思考的问题
class A
{
int i;
int j;
char c;
double d;
};
sizeof(A)=?
struct B
{
int i;
int j;
char c;
double d;
};
sizeof(B)=?
例 1 对象内存布局初探
#include
#include
using namespace std;
class A
{
int i;
int j;
char c;
double d;
public:
void print()
{
cout << "i = " << i << ", "
<< "j = " << j << ", "
<< "c = " << c << ", "
<< "d = " << d << endl;
}
};
struct B
{
int i;
int j;
char c;
double d;
};
int main()
{
A a;
cout << "sizeof(A) = " << sizeof(A) << endl; // 20 bytes
cout << "sizeof(a) = " << sizeof(a) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl; // 20 bytes
a.print();
B* p = reinterpret_cast(&a);
p->i = 1;
p->j = 2;
p->c = 'c';
p->d = 3;
a.print();
p->i = 100;
p->j = 200;
p->c = 'C';
p->d = 3.14;
a.print();
return 0;
}
运行时的对象退化为结构体的形式
所有成员变量在内存中依次排布
成员变量间可能存在内存间隙
可以通过内存地址直接访问成员变量
访问权限关键字在运行时失效
类中的成员函数位于代码段中
调用成员函数时对象地址作为参数隐式传递
成员函数通过对象地址访问成员变量
c++语法规则隐藏了对象地址的传递过程
例 2 对象本质分析
#include
#include
using namespace std;
class Demo
{
int mi;
int mj;
public:
Demo(int i, int j)
{
mi = i;
mj = j;
}
int getI()
{
return mi;
}
int getJ()
{
return mj;
}
int add(int value)
{
return mi + mj + value;
}
};
int main()
{
Demo d(1, 2);
cout << "sizeof(d) = " << sizeof(d) << endl;
cout << "d.getI() = " << d.getI() << endl;
cout << "d.getJ() = " << d.getJ() << endl;
cout << "d.add(3) = " << d.add(3) << endl;
return 0;
}
例 3 对象本质分析
#ifndef _50_2_H_
#define _50_2_H_
typedef void Demo;
Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);
#endif
#include "50-2.h"
#include "malloc.h"
struct ClassDemo
{
int mi;
int mj;
};
Demo* Demo_Create(int i, int j)
{
struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
if( ret != NULL )
{
ret->mi = i;
ret->mj = j;
}
return ret;
}
int Demo_GetI(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi;
}
int Demo_GetJ(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mj;
}
int Demo_Add(Demo* pThis, int value)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi + obj->mj + value;
}
void Demo_Free(Demo* pThis)
{
free(pThis);
}
#include
#include "50-2.h"
int main()
{
Demo* d = Demo_Create(1, 2); // Demo* d = new Demo(1, 2);
printf("d.mi = %d\n", Demo_GetI(d)); // d->getI();
printf("d.mj = %d\n", Demo_GetJ(d)); // d->getJ();
printf("Add(3) = %d\n", Demo_Add(d, 3)); // d->add(3);
// d->mi = 100;
Demo_Free(d);
return 0;
}
小结:
C++中的类对象在内存布局上与结构体相同
成员变量和成员函数在内存中分开存放
访问权限关键字在运行时失效
调用成员函数时对象地址作为参数隐式传递
在 C++编译器的内部类可以理解为结构体
子类是由父类成员叠加子类新成员得到的
class Derived:public Demo
{
int mk;
};
例 1 继承对象模型初探
#include
#include
using namespace std;
class Demo
{
protected:
int mi;
int mj;
public:
virtual void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << endl;
}
};
class Derived : public Demo
{
int mk;
public:
Derived(int i, int j, int k)
{
mi = i;
mj = j;
mk = k;
}
void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << ", "
<< "mk = " << mk << endl;
}
};
struct Test
{
void* p;
int mi;
int mj;
int mk;
};
int main()
{
cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
Derived d(1, 2, 3);
Test* p = reinterpret_cast(&d);
cout << "Before changing ..." << endl;
d.print();
p->mi = 10;
p->mj = 20;
p->mk = 30;
cout << "After changing ..." << endl;
d.print();
return 0;
}
C++多态的实现原理
当类中声明虚函数时,编译器会在类中生成一个虚函数表
虚函数表是一个存储成员函数地址的数据结构
虚函数表是由编译器自动生成与维护的
virtual 成员函数会被编译器放入虚函数表中
存在虚 函数时,每个对象中都有一个指向虚函数表的指针
class demo
{
int mi,mj;
public:
virtual int add(int value)
{
return mi+mj+value;
}
};
class Derived:public Demo
{
int mk;
public:
virtual int add(int value)
{
return mk+value;
}
};
void run(Demo* p,int v)
{
p->add(v);
}
编译器确认 add()是否为虚函数
1、yes->编译器在对象 VPTR 所指向的虚函数表中查找 add()的地址
2、no->编译器直接可以直接确定被调用成员函数的地址
调用效率对比:虚函数<普通成员函数
例 2 多态本质分析
#ifndef _51_2_H_
#define _51_2_H_
typedef void Demo;
typedef void Derived;
Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);
Derived* Derived_Create(int i, int j, int k);
int Derived_GetK(Derived* pThis);
int Derived_Add(Derived* pThis, int value);
#endif
#include "51-2.h"
#include "malloc.h"
static int Demo_Virtual_Add(Demo* pThis, int value);
static int Derived_Virtual_Add(Demo* pThis, int value);
struct VTable // 2. 定义虚函数表数据结构
{
int (*pAdd)(void*, int); // 3. 虚函数表里面存储什么???
};
struct ClassDemo
{
struct VTable* vptr; // 1. 定义虚函数表指针 ==》 虚函数表指针类型???
int mi;
int mj;
};
struct ClassDerived
{
struct ClassDemo d;
int mk;
};
static struct VTable g_Demo_vtbl =
{
Demo_Virtual_Add
};
static struct VTable g_Derived_vtbl =
{
Derived_Virtual_Add
};
Demo* Demo_Create(int i, int j)
{
struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
if( ret != NULL )
{
ret->vptr = &g_Demo_vtbl; // 4. 关联对象和虚函数表
ret->mi = i;
ret->mj = j;
}
return ret;
}
int Demo_GetI(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi;
}
int Demo_GetJ(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mj;
}
// 6. 定义虚函数表中指针所指向的具体函数
static int Demo_Virtual_Add(Demo* pThis, int value)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi + obj->mj + value;
}
// 5. 分析具体的虚函数!!!!
int Demo_Add(Demo* pThis, int value)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->vptr->pAdd(pThis, value);
}
void Demo_Free(Demo* pThis)
{
free(pThis);
}
Derived* Derived_Create(int i, int j, int k)
{
struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
if( ret != NULL )
{
ret->d.vptr = &g_Derived_vtbl;
ret->d.mi = i;
ret->d.mj = j;
ret->mk = k;
}
return ret;
}
int Derived_GetK(Derived* pThis)
{
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->mk;
}
static int Derived_Virtual_Add(Demo* pThis, int value)
{
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->mk + value;
}
int Derived_Add(Derived* pThis, int value)
{
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->d.vptr->pAdd(pThis, value);
}
#include "stdio.h"
#include "51-2.h"
void run(Demo* p, int v)
{
int r = Demo_Add(p, v);
printf("r = %d\n", r);
}
int main()
{
Demo* pb = Demo_Create(1, 2);
Derived* pd = Derived_Create(1, 22, 333);
printf("pb->add(3) = %d\n", Demo_Add(pb, 3));
printf("pd->add(3) = %d\n", Derived_Add(pd, 3));
run(pb, 3);
run(pd, 3);
Demo_Free(pb);
Demo_Free(pd);
return 0;
}
小结:
继承的本质就是父子间成员变量的叠加
c++中的多态是通过虚函数表实现的
虚函数表是由编译器自动生成与维护的
虚函数的调用效率低于普通成员函数
面向对象中的抽象概念
在进行面向对象分析时,会发现一些抽象的概念!
在现实中需要知道具体的图形类型才能求面积,所以对概念上的“图形”求面积是没有意义
的!
class Shape
{
public:
double area()
{
return 0;
}
};
Shape 类有必要存在吗?
面向对象中的抽象类
可用于表示现实世界中的抽象概念
是一种只能定义类型,而不能产生对象的类
只能被继承并重写相关函数
直接特征是相关函数没有完整的实现
Shape 是世界上各种图形的抽象概念
因此:
程序中必须能够反映抽象的图形
程序中通过抽象类表示图形的概念
抽象类不能创建对象,只能用于继承
C++语言中没有抽象类的概念
C++中通过纯虚函数实现抽象类
纯虚函数是指只定义原型的成员函数
一个 C++类中存在纯虚函数就成为了抽象类
纯虚函数的语法规则
class Shape
{
public:
virtual double area()=0;
};
“=0”用于高速编译器当前是声明纯虚函数,因此不需要定义函数体
例 1 抽象类初探
#include
#include
using namespace std;
class Shape
{
public:
virtual double area() = 0;
};
class Rect : public Shape
{
int ma;
int mb;
public:
Rect(int a, int b)
{
ma = a;
mb = b;
}
double area()
{
return ma * mb;
}
};
class Circle : public Shape
{
int mr;
public:
Circle(int r)
{
mr = r;
}
double area()
{
return 3.14 * mr * mr;
}
};
void area(Shape* p)
{
double r = p->area();
cout << "r = " << r << endl;
}
int main()
{
Rect rect(1, 2);
Circle circle(10);
area(&rect);
area(&circle);
return 0;
}
抽象类只能用作父类被继承
子类必须实现纯虚函数的具体功能
纯虚函数被实现后成为虚函数
如果子类没有实现纯虚函数,则子类成为抽象类
满足下面条件的 C++类则称为接口
类中没有定义任何的成员变量
所有的成员函数都是公有的
所有的成员函数都是纯虚函数
接口是一种特殊的抽象类
例 2 接口初探
#include
#include
using namespace std;
class Channel
{
public:
virtual bool open() = 0;
virtual void close() = 0;
virtual bool send(char* buf, int len) = 0;
virtual int receive(char* buf, int len) = 0;
};
int main()
{
return 0;
}
小结:
抽象类用于描述现实世界中的抽象概念
抽象类只能被继承不能创建对象
C++中没有抽象类的概念
C++中通过纯虚函数实现抽象类
类中只存在纯虚函数时称为接口
接口是一种特殊的抽象类
C++是否允许一个类继承自多个父类
C++支持编写多重继承的代码
一个子类可以拥有多个子类
子类拥有所有父类的成员变量
子类继承所有父类的成员函数
子类对象可以当作任意父类对象使用
多重继承的语法规则
class Derived:public BaseA,public BaseB,public BaseC
{
//….
}
多重继承的本质与单继承相同!
例 1 多重继承问题一
#include
#include
using namespace std;
class BaseA
{
int ma;
public:
BaseA(int a)
{
ma = a;
}
int getA()
{
return ma;
}
};
class BaseB
{
int mb;
public:
BaseB(int b)
{
mb = b;
}
int getB()
{
return mb;
}
};
class Derived : public BaseA, public BaseB
{
int mc;
public:
Derived(int a, int b, int c) : BaseA(a), BaseB(b)
{
mc = c;
}
int getC()
{
return mc;
}
void print()
{
cout << "ma = " << getA() << ", "
<< "mb = " << getB() << ", "
<< "mc = " << mc << endl;
}
};
int main()
{
cout << "sizeof(Derived) = " << sizeof(Derived) << endl; // 12
Derived d(1, 2, 3);
d.print();
cout << "d.getA() = " << d.getA() << endl;
cout << "d.getB() = " << d.getB() << endl;
cout << "d.getC() = " << d.getC() << endl;
cout << endl;
BaseA* pa = &d;
BaseB* pb = &d;
cout << "pa->getA() = " << pa->getA() << endl;
cout << "pb->getB() = " << pb->getB() << endl;
cout << endl;
void* paa = pa;
void* pbb = pb;
if( paa == pbb )
{
cout << "Pointer to the same object!" << endl;
}
else
{
cout << "Error" << endl;
}
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "paa = " << paa << endl;
cout << "pbb = " << pbb << endl;
return 0;
}
通过多重继承得到的对象可能拥有“不同的地址”。
多重继承可能产生冗余的成员
例 2 多重继承问题二
#include
#include
using namespace std;
class People
{
string m_name;
int m_age;
public:
People(string name, int age)
{
m_name = name;
m_age = age;
}
void print()
{
cout << "Name = " << m_name << ", "
<< "Age = " << m_age << endl;
}
};
class Teacher : virtual public People
{
public:
Teacher(string name, int age) : People(name, age)
{
}
};
class Student : virtual public People
{
public:
Student(string name, int age) : People(name, age)
{
}
};
class Doctor : public Teacher, public Student
{
public:
Doctor(string name, int age) : Teacher(name, age), Student(name, age), People(name, age)
{
}
};
int main()
{
Doctor d("Delphi", 33);
d.print();
return 0;
}
当多重继承关系出现闭合时将产生数据冗余的问题
解决方案:虚继承
class people{};
class teacher:virtual public people{};
class student:virtual public people{};
class doctor:public teacher,public student
{
};
虚继承能够解决数据冗余问题
中间层父类不再关心顶层父类的初始化
最终类必须直接调用顶层父类的构造函数
问题:
当架构设计中需要继承时,无法确定使用直接继承还是虚继承
小结:
C++支持多重继承的编程方式
多重继承容易带来问题
可能出现“同一个对象的地址不同”的情况
虚继承可以解决数据冗余的问题
虚继承的使得架构设计可能出现问题
多重继承可能产生多个虚函数表
例 1 多重继承问题三
#include
#include
using namespace std;
class BaseA
{
public:
virtual void funcA()
{
cout << "BaseA::funcA()" << endl;
}
};
class BaseB
{
public:
virtual void funcB()
{
cout << "BaseB::funcB()" << endl;
}
};
class Derived : public BaseA, public BaseB
{
};
int main()
{
Derived d;
BaseA* pa = &d;
BaseB* pb = &d;
BaseB* pbe = (BaseB*)pa; // oops!!
BaseB* pbc = dynamic_cast(pa);
cout << "sizeof(d) = " << sizeof(d) << endl;
cout << "Using pa to call funcA()..." << endl;
pa->funcA();
cout << "Using pb to call funcB()..." << endl;
pb->funcB();
cout << "Using pbc to call funcB()..." << endl;
pbc->funcB();
cout << endl;
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "pbe = " << pbe << endl;
cout << "pbc = " << pbc << endl;
return 0;
}
需要进行强制类型转换时,C++中推介使用新式类型转换关键字
解决方案:dynamic_case
Derived d;
BaseA* pa=&d;
BaseB* pb=&d;
BaseB* pbb=(BaseB*)pa;
工程开发中的“多重继承”方式
单继承某个类+实现(多个)接口
例 2 正确的多继承方式
#include
#include
using namespace std;
class Base
{
protected:
int mi;
public:
Base(int i)
{
mi = i;
}
int getI()
{
return mi;
}
bool equal(Base* obj)
{
return (this == obj);
}
};
class Interface1
{
public:
virtual void add(int i) = 0;
virtual void minus(int i) = 0;
};
class Interface2
{
public:
virtual void multiply(int i) = 0;
virtual void divide(int i) = 0;
};
class Derived : public Base, public Interface1, public Interface2
{
public:
Derived(int i) : Base(i)
{
}
void add(int i)
{
mi += i;
}
void minus(int i)
{
mi -= i;
}
void multiply(int i)
{
mi *= i;
}
void divide(int i)
{
if( i != 0 )
{
mi /= i;
}
}
};
int main()
{
Derived d(100);
Derived* p = &d;
Interface1* pInt1 = &d;
Interface2* pInt2 = &d;
cout << "p->getI() = " << p->getI() << endl; // 100
pInt1->add(10);
pInt2->divide(11);
pInt1->minus(5);
pInt2->multiply(8);
cout << "p->getI() = " << p->getI() << endl; // 40
cout << endl;
cout << "pInt1 == p : " << p->equal(dynamic_cast (pInt1)) << endl;
cout << "pInt2 == p : " << p->equal(dynamic_cast (pInt2)) << endl;
return 0;
}
一些有用的工程建议
先继承自一个父类,然后实现多个接口
父类提供 equal()成员函数
equal()成员函数用于判断指针是否指向当前对象
与多重继承相关的强制类型转换用 dynamic_cast 完成
小结:
多继承中可能出现多个虚函数表指针
与多重继承相关的强制类型转换用 dynamic_cast 完成
工程开发中采用“单继承多接口”的方式使用多继承
父类提供成员函数用于判断指针是否指向当前对象
new 和 malloc 的区别是什么?
delete 和 free 的区别是什么?
new 关键字与 malloc 函数的区别
new 关键字是 C++的一部分
malloc 是由 C 库提供的函数
new 以具体类型为单位进行内存分配
malloc 以字节为单位进行内存分配
new 在申请内存空间时可进行初始化
malloc 仅根据需要申请定量的内存空间
下面的代码输出什么?为什么?
class Test
{
public:
Test()
{
cout<<”Test()”<
#include
#include
#include
using namespace std;
class Test
{
int* mp;
public:
Test()
{
cout << "Test::Test()" << endl;
mp = new int(100);
cout << *mp << endl;
}
~Test()
{
delete mp;
cout << "~Test::Test()" << endl;
}
};
int main()
{
Test* pn = new Test;
Test* pm = (Test*)malloc(sizeof(Test));
delete pn;
free(pm);
return 0;
}
new 和 malloc 的区别
new 在所有 C++编译器中被支持
malloc 在某些系统开发中是不能调用的
new 能够触发构造函数的调用
malloc 仅分配需要的内存空间
对象的创建只能使用 new
malloc 不适合面向对象的开发
下面的代码输出什么?为什么?
int main()
{
Test *pn=new Test;
Test pm=(Test )malloc(sizeof(Test));
delete pn;
free(pm);
return 0;
}
delete 和 free 的区别
delete 在所有 C++编译器中被支持
free 在某些系统开发中是不能调用
delete 能够触发析构函数的调用
free 仅归还之前分配的内存空间
对象的销毁只能使用 delete
free 不适合面向对象开发
构造函数是否可以成为虚函数?
析构函数是否可以成为虚函数?
构造函数不可能成为虚函数
在构造函数执行结束后,虚函数表指针才会被正确的初始化
析构函数可以成为虚函数
建议在设计类时将析构函数声明为虚函数
例 2 构造,析构,虚函数
#include
#include
using namespace std;
class Base
{
public:
Base()
{
cout << "Base()" << endl;
func();
}
virtual void func()
{
cout << "Base::func()" << endl;
}
virtual ~Base()
{
func();
cout << "~Base()" << endl;
}
};
class Derived : public Base
{
public:
Derived()
{
cout << "Derived()" << endl;
func();
}
virtual void func()
{
cout << "Derived::func()" << endl;
}
~Derived()
{
func();
cout << "~Derived()" << endl;
}
};
int main()
{
Base* p = new Derived();
// ...
delete p;
return 0;
}
构造函数中是否可以发生多态?
析构函数中是否可以发生多态?
构造函数中不可能发生多态行为
在构造函数执行时,虚函数表指针未被正确初始化
析构函数中不可能发生多态行为
在析构函数执行时,虚函数表指针已经被销毁
构造函数和析构函数中不能发生多态行为,只调用当前类中定义的函数版本!
继承中如何正确的使用强制类型转换?
dynamic_cast 是与继承相关的类型转换关键字
dynamic_cast 要求相关的类中必须有虚函数
用于直接或者间接继承关系的指针(引用)之间
指针:
转换成功:得到目标类型的指针
转换失败:得到一个空指针
引用:
转换成功:得到目标类型的引用
转换失败:得到一个异常操作信息
编译器会检查 dynamic_cast 的使用是否正确
类型转换的结果只能在运行阶段才能得到
例 3 dynamic_cast 的使用
#include
#include
using namespace std;
class Base
{
public:
Base()
{
cout << "Base::Base()" << endl;
}
virtual ~Base()
{
cout << "Base::~Base()" << endl;
}
};
class Derived : public Base
{
};
int main()
{
Base* p = new Base;
Derived* pd = dynamic_cast(p);
if( pd != NULL )
{
cout << "pd = " << pd << endl;
}
else
{
cout << "Cast error!" << endl;
}
delete p;
return 0;
}
小结:
new/delete 会触发构造函数或者析构函数的调用
构造函数不能成为虚函数
析构函数可以成为虚函数
构造函数和析构函数中都无法产生多态行为
dynamic_cast 是与继承相关的专用转换关键字
C++中有几种交换变量的方法?
定义宏代码块 vs 定义函数
例 1 变量的交换
#include
#include
using namespace std;
#define SWAP(t, a, b) \
do \
{ \
t c = a; \
a = b; \
b = c; \
}while(0)
void Swap(int& a, int& b)
{
int c = a;
a = b;
b = c;
}
void Swap(double& a, double& b)
{
double c = a;
a = b;
b = c;
}
void Swap(string& a, string& b)
{
string c = a;
a = b;
b = c;
}
int main()
{
int a = 0;
int b = 1;
Swap(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
double m = 2;
double n = 3;
Swap(m, n);
cout << "m = " << m << endl;
cout << "n = " << n << endl;
string d = "Delphi";
string t = "Tang";
Swap(d, t);
cout << "d = " << d << endl;
cout << "t = " << t << endl;
return 0;
}
定义宏代码块
优点:代码复用,适合所有的类型
缺点:编译器不知道宏的存在,缺少类型检查
定义函数
优点:真正的函数调用,编译器对类型进行检查
缺点:根据类型重复定义函数,无法代码复用
C++中有没有解决方案集合,两种方法的优点?
泛型编程的概念
不考虑具体数据类型的编程方式
对于 swap 函数可以考虑下面的泛型写法
void swap(T& a,T& b)
{
T t=a;
a=b;
b=t;
}
swap 泛型写法中的 T 不是一个具体的数据类型,而是任意的数据类型
C++中泛型编程
函数模板
一种特殊的函数可用不同类型进行调用
看起来和普通函数很相似,区别是类型可被参数化
template
void swap(T& a,T& b)
{
T t=a;
a=b;
b=t;
}
函数模板的语法规则
template 关键字用于声明开始进行泛型编程
typename 关键字用于声明泛型类型
函数模板的使用
自动类型推导调用
具体类型显示调用表
int a=0;
int b=1;
swap(a,b); //自动推导
float c=2;
float d=3;
swap(c,d); //显示调用
例 2 函数模板使用初探
#include
#include
using namespace std;
template < typename T >
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
template < typename T >
void Sort(T a[], int len)
{
for(int i=0; ifor(int j=i; jif( a[i] > a[j] )
{
Swap(a[i], a[j]);
}
}
}
}
template < typename T >
void Println(T a[], int len)
{
for(int i=0; icout << a[i] << ", ";
}
cout << endl;
}
int main()
{
int a[5] = {5, 3, 2, 4, 1};
Println(a, 5);
Sort(a, 5);
Println(a, 5);
string s[5] = {"Java", "C++", "Pascal", "Ruby", "Basic"};
Println(s, 5);
Sort(s, 5);
Println(s, 5);
return 0;
}
小结:
函数模板时泛型编程在 C++中的应用方式之一
函数模板能够根据实参对参数类型进行推导
函数模板支持显示的指定参数类型
函数模板时 C++中重要的代码复用方式
函数模板深入理解
编译器从函数模板通过具体类型产生不同的函数
编译器会对函数模板进行两次编译
对模板代码本身进行编译
对参数替换后的代码进行编译
注意事项
函数模板推导类型时,必须严格匹配
自动推导类型时,必须严格匹配
显示类型指定时,能够进行隐式类型转换
例 1 函数模板的本质
#include
#include
using namespace std;
class Test
{
Test(const Test&);
public:
Test()
{
}
};
template < typename T >
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
typedef void(FuncI)(int&, int&);
typedef void(FuncD)(double&, double&);
typedef void(FuncT)(Test&, Test&);
int main()
{
FuncI* pi = Swap; // 编译器自动推导 T 为 int
FuncD* pd = Swap; // 编译器自动推导 T 为 double
// FuncT* pt = Swap; // 编译器自动推导 T 为 Test
cout << "pi = " << reinterpret_cast<void*>(pi) << endl;
cout << "pd = " << reinterpret_cast<void*>(pd) << endl;
// cout << "pt = " << reinterpret_cast(pt) << endl;
return 0;
}
函数模板可以定义任意多个不同的类型参数
template
#include
#include
using namespace std;
template
< typename T1, typename T2, typename T3 >
T1 Add(T2 a, T3 b)
{
return static_cast(a + b);
}
int main()
{
// T1 = int, T2 = double, T3 = double
int r1 = Add<int>(0.5, 0.8);
// T1 = double, T2 = float, T3 = double
double r2 = Add<double, float>(0.5, 0.8);
// T1 = float, T2 = float, T3 = float
float r3 = Add<float, float, float>(0.5, 0.8);
cout << "r1 = " << r1 << endl; // r1 = 1
cout << "r2 = " << r2 << endl; // r2 = 1.3
cout << "r3 = " << r3 << endl; // r3 = 1.3
return 0;
}
当桉树重载遇见函数模板会发生什么?
函数模板可以像普通函数一样被重载
C++编译器优先考虑普通函数
如果函数模板可以产生一个更好的匹配,那么选择模板
可以通过空模板实参列表限定编译器只匹配模板
int r1=Max(1,2);
double r2=Max<>(0.5,0.8);
限定编译器只匹配函数模板
例 3 重载函数模板
#include
#include
using namespace std;
template < typename T >
T Max(T a, T b)
{
cout << "T Max(T a, T b)" << endl;
return a > b ? a : b;
}
int Max(int a, int b)
{
cout << "int Max(int a, int b)" << endl;
return a > b ? a : b;
}
template < typename T >
T Max(T a, T b, T c)
{
cout << "T Max(T a, T b, T c)" << endl;
return Max(Max(a, b), c);
}
int main()
{
int a = 1;
int b = 2;
cout << Max(a, b) << endl; // 普通函数 Max(int, int)
cout << Max<>(a, b) << endl; // 函数模板 Max(int, int)
cout << Max(3.0, 4.0) << endl; // 函数模板 Max(double, double)
cout << Max(5.0, 6.0, 7.0) << endl; // 函数模板 Max(double, double, double)
cout << Max('a', 100) << endl; // 普通函数 Max(int, int)
return 0;
}
小结:
函数模板通过具体类型产生不同的函数
函数模板可以定义任意多个不同的类型参数
函数模板中的返回值类型必须显示指定
函数模板可以像普通函数一样被重载
在 C++中是否能够将泛型的思想应用于类
一些类主要用于存储和组织数据元素
类中数据组织的方式和数据元素的具体类型无关
如:数组类,链表类,Stack 类,Queue 类,等
C++中将模板的思想应用于类,使得类的实现不关注数据元素的具体类型,而只关注类所需
要实现的功能
C++中的类模板
以相同的方式处理不同的类型
在类声明前使用 template 进行标识
用于说明类中使用的泛指类型 T
template
class Operator
{
public:
T op(T a,T b);
};
类模板的应用
只能显示指定具体类型,无法自动推导
使用具体类型定义对象
Operatorop1;
Operatorop2;
int i=op1.op(1,2);
string s=op2.op(“baby”,”software”);
声明的泛指类型 T 可以出现在类模板的任意地方
编译器对类模板的处理方式和函数模板相同
从模板通过具体类型产生不同的类
在声明的地方对类模板代码本身进行编译
在使用的地方对参数替换后的代码进行编译
例 1 类模板初探
#include
#include
using namespace std;
template < typename T >
class Operator
{
public:
T add(T a, T b)
{
return a + b;
}
T minus(T a, T b)
{
return a - b;
}
T multiply(T a, T b)
{
return a * b;
}
T divide(T a, T b)
{
return a / b;
}
};
string operator-(string& l, string& r)
{
return "Minus";
}
int main()
{
Operator<int> op1;
cout << op1.add(1, 2) << endl;
Operator<string> op2;
cout << op2.add("D.T.", "Software") << endl;
cout << op2.minus("D.T", "Software") << endl;
return 0;
}
类模板的工程应用
类模板必须在头文件中定义
类模板不能分开实现不同的文件中
类模板外部定义的成员函数需要加上模板<>声明
例 2 模板类的工程应用
#ifndef _OPERATOR_H_
#define _OPERATOR_H_
template < typename T >
class Operator
{
public:
T add(T a, T b);
T minus(T a, T b);
T multiply(T a, T b);
T divide(T a, T b);
};
template < typename T >
T Operator<T>::add(T a, T b)
{
return a + b;
}
template < typename T >
T Operator<T>::minus(T a, T b)
{
return a - b;
}
template < typename T >
T Operator<T>::multiply(T a, T b)
{
return a * b;
}
template < typename T >
T Operator<T>::divide(T a, T b)
{
return a / b;
}
#endif
#include
#include
#include "Operator.h"
using namespace std;
int main()
{
Operator<int> op1;
cout << op1.add(1, 2) << endl;
cout << op1.multiply(4, 5) << endl;
cout << op1.minus(5, 6) << endl;
cout << op1.divide(10, 5) << endl;
return 0;
}
小结:
泛型编程的思想可以应用于类
类模板以相同的方式处理不同类型的数据
类模板非常适用于编写数据结构相关的代码
类模板在使用时只能显示指定类型
类模板可以定义任意多个不同的类型参数
template
#include
#include
using namespace std;
template
< typename T1, typename T2 >
class Test
{
public:
void add(T1 a, T2 b)
{
cout << "void add(T1 a, T2 b)" << endl;
cout << a + b << endl;
}
};
template
< typename T1, typename T2 >
class Test < T1*, T2* > // 关于指针的特化实现
{
public:
void add(T1* a, T2* b)
{
cout << "void add(T1* a, T2* b)" << endl;
cout << *a + *b << endl;
}
};
template
< typename T >
class Test < T, T > // 当 Test 类模板的两个类型参数完全相同时,使用这个实现
{
public:
void add(T a, T b)
{
cout << "void add(T a, T b)" << endl;
cout << a + b << endl;
}
void print()
{
cout << "class Test < T, T >" << endl;
}
};
template
< >
class Test < void*, void* > // 当 T1 == void* 并且 T2 == void* 时
{
public:
void add(void* a, void* b)
{
cout << "void add(void* a, void* b)" << endl;
cout << "Error to add void* param..." << endl;
}
};
int main()
{
Test<int, float> t1;
Test<long, long> t2;
Test<void*, void*> t3;
t1.add(1, 2.5);
t2.add(5, 5);
t2.print();
t3.add(NULL, NULL);
Test<int*, double*> t4;
int a = 1;
double b = 0.1;
t4.add(&a, &b);
return 0;
}
类模板特化注意事项
特化只是模板的分开实现
本质上是同一个类模板
特化类模板的使用方式是统一的
必须显示指定每一个类型参数
类模板特化与重定义有区别吗?
函数模板可以特化吗?
重定义和特化的不同
重定义
一个类模板和一个新类(或者两个类模板)
使用的时候需要考虑如何选择的问题
特化
以统一的方式使用类模板和特化类
编译器自动优先选择特化类
函数模板只支持类型参数完全特化
template //函数模板定义
bool Equal(T a,T b)
{
return a==b;
}
template <> //函数模板完全特化
bool Equal
#include
#include
using namespace std;
template
< typename T1, typename T2 >
class Test
{
public:
void add(T1 a, T2 b)
{
cout << "void add(T1 a, T2 b)" << endl;
cout << a + b << endl;
}
};
/*
template
< >
class Test < void*, void* > // 当 T1 == void* 并且 T2 == void* 时
{
public:
void add(void* a, void* b)
{
cout << "void add(void* a, void* b)" << endl;
cout << "Error to add void* param..." << endl;
}
};
*/
class Test_Void
{
public:
void add(void* a, void* b)
{
cout << "void add(void* a, void* b)" << endl;
cout << "Error to add void* param..." << endl;
}
};
template
< typename T >
bool Equal(T a, T b)
{
cout << "bool Equal(T a, T b)" << endl;
return a == b;
}
template
< >
bool Equal<double>(double a, double b)
{
const double delta = 0.00000000000001;
double r = a - b;
cout << "bool Equal(double a, double b)" << endl;
return (-delta < r) && (r < delta);
}
bool Equal(double a, double b)
{
const double delta = 0.00000000000001;
double r = a - b;
cout << "bool Equal(double a, double b)" << endl;
return (-delta < r) && (r < delta);
}
int main()
{
cout << Equal( 1, 1 ) << endl;
cout << Equal<>( 0.001, 0.001 ) << endl;
return 0;
}
当需要重载函数模板时,优先考虑使用模板特化;当模板特化无法满足需求,再使用函数重
载!
小结:
类模板可以定义多个不同的类型参数
类模板可以被部分特化和完全特化
特化的本质是模板的分开实现
函数模板只支持完全特化
工程中使用模板特化代替类(函数)重定义
模板参数可以是数值型参数(非类型参数)
template
#include
#include
#include "Array.h"
#include "HeapArray.h"
using namespace std;
int main()
{
Array<double, 5> ad;
for(int i=0; ifor(int i=0; icout << ad[i] << endl;
}
cout << endl;
HeapArray<char>* pai = HeapArray<char>::NewInstance(10);
if( pai != NULL )
{
HeapArray<char>& ai = pai->self();
for(int i=0; i'a';
}
for(int i=0; icout << ai[i] << endl;
}
}
delete pai;
return 0;
}
例 1 数值型模板参数
#include
#include
using namespace std;
template
< typename T, int N >
void func()
{
T a[N] = {0};
for(int i=0; ifor(int i=0; icout << a[i] << endl;
}
}
template
< int N >
class Sum
{
public:
static const int VALUE = Sum1>::VALUE + N;
};
template
< >
class Sum < 1 >
{
public:
static const int VALUE = 1;
};
int main()
{
cout << "1 + 2 + 3 + ... + 10 = " << Sum<10>::VALUE << endl;
cout << "1 + 2 + 3 + ... + 100 = " << Sum<100>::VALUE << endl;
return 0;
}
例 2 数组模板类
#ifndef _ARRAY_H_
#define _ARRAY_H_
template
< typename T, int N >
class Array
{
T m_array[N];
public:
int length();
bool set(int index, T value);
bool get(int index, T& value);
T& operator[] (int index);
T operator[] (int index) const;
virtual ~Array();
};
template
< typename T, int N >
int Array::length()
{
return N;
}
template
< typename T, int N >
bool Array::set(int index, T value)
{
bool ret = (0 <= index) && (index < N);
if( ret )
{
m_array[index] = value;
}
return ret;
}
template
< typename T, int N >
bool Array::get(int index, T& value)
{
bool ret = (0 <= index) && (index < N);
if( ret )
{
value = m_array[index];
}
return ret;
}
template
< typename T, int N >
T& Array::operator[] (int index)
{
return m_array[index];
}
template
< typename T, int N >
T Array::operator[] (int index) const
{
return m_array[index];
}
template
< typename T, int N >
Array::~Array()
{
}
#endif
例 3 堆数组模板类
#ifndef _HEAPARRAY_H_
#define _HEAPARRAY_H_
template
< typename T >
class HeapArray
{
private:
int m_length;
T* m_pointer;
HeapArray(int len);
HeapArray(const HeapArray& obj);
bool construct();
public:
static HeapArray* NewInstance(int length);
int length();
bool get(int index, T& value);
bool set(int index ,T value);
T& operator [] (int index);
T operator [] (int index) const;
HeapArray& self();
~HeapArray();
};
template
< typename T >
HeapArray::HeapArray(int len)
{
m_length = len;
}
template
< typename T >
bool HeapArray::construct()
{
m_pointer = new T[m_length];
return m_pointer != NULL;
}
template
< typename T >
HeapArray* HeapArray::NewInstance(int length)
{
HeapArray* ret = new HeapArray(length);
if( !(ret && ret->construct()) )
{
delete ret;
ret = 0;
}
return ret;
}
template
< typename T >
int HeapArray::length()
{
return m_length;
}
template
< typename T >
bool HeapArray::get(int index, T& value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
value = m_pointer[index];
}
return ret;
}
template
< typename T >
bool HeapArray::set(int index, T value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
m_pointer[index] = value;
}
return ret;
}
template
< typename T >
T& HeapArray::operator [] (int index)
{
return m_pointer[index];
}
template
< typename T >
T HeapArray::operator [] (int index) const
{
return m_pointer[index];
}
template
< typename T >
HeapArray& HeapArray::self()
{
return *this;
}
template
< typename T >
HeapArray::~HeapArray()
{
delete[]m_pointer;
}
#endif
小结:
模板参数可以是数值型参数
数值型模板参数必须在编译期间唯一确定
数组类模板是基于数值型模板参数实现的
数组类模板是简易的线性表数据结构
智能指针的意义
现代 C++开发库中最重要的类模板之一
C++中自动内存管理的主要手段
能够在很大程度上避开内存相关的问题
STL 中的智能指针 auto_ptr
生命周期结束时,销毁指向的内存空间
不能指向堆数组,只能指向堆对象(变量)
一片堆空间只属于一个智能指针对象
多个智能指针对象不能指向同一片堆空间
例 1 auto_ptr 使用初探
#include
#include
#include
using namespace std;
class Test
{
string m_name;
public:
Test(const char* name)
{
cout << "Hello, " << name << "." << endl;
m_name = name;
}
void print()
{
cout << "I'm " << m_name << "." << endl;
}
~Test()
{
cout << "Goodbye, " << m_name << "." << endl;
}
};
int main()
{
auto_ptr pt(new Test("D.T.Software"));
cout << "pt = " << pt.get() << endl;
pt->print();
cout << endl;
auto_ptr pt1(pt);
cout << "pt = " << pt.get() << endl;
cout << "pt1 = " << pt1.get() << endl;
pt1->print();
return 0;
}
STL 中的其他智能指针
shared_ptr
带有引用计数机制,支持多个指针对象指向同一片内存
weak_ptr
配合 shared_ptr 而引入的一种智能指针
unique_ptr
一个指针对象指向一片内存空间,不能拷贝构造和赋值
Qt 中的智能指针
QPointer
当其指向的对象被销毁时,它会被自动置空
QSharedPointer
引用计数型智能指针
可以被自由地拷贝和赋值
当引用计数为 0 时才删除指向的对象
例 2 Qt 中的智能指针
#include
#include
#include
class Test : public QObject
{
QString m_name;
public:
Test(const char* name)
{
qDebug() << "Hello, " << name << ".";
m_name = name;
}
void print()
{
qDebug() << "I'm " << m_name << ".";
}
~Test()
{
qDebug() << "Goodbye, " << m_name << ".";
}
};
int main()
{
QPointer pt(new Test("D.T.Software"));
QPointer pt1(pt);
QPointer pt2(pt);
pt->print();
pt1->print();
pt2->print();
delete pt;
qDebug() << "pt = " << pt;
qDebug() << "pt1 = " << pt1;
qDebug() << "pt2 = " << pt2;
qDebug() << endl;
QSharedPointer spt(new Test("Delphi Tang"));
QSharedPointer spt1(spt);
QSharedPointer spt2(spt);
spt->print();
spt1->print();
spt2->print();
return 0;
}
Qt 中的其他智能指针
QWeakPointer
QScopedPointer
QScopedArrayPointer
QSharedDataPointer
QExplicitlySharedDataPointer
例 3 创建智能指针类模板
#ifndef _SMARTPOINTER_H_
#define _SMARTPOINTER_H_
template
< typename T >
class SmartPointer
{
T* mp;
public:
SmartPointer(T* p = NULL)
{
mp = p;
}
SmartPointer(const SmartPointer& obj)
{
mp = obj.mp;
const_cast&>(obj).mp = NULL;
}
SmartPointer& operator = (const SmartPointer& obj)
{
if( this != &obj )
{
delete mp;
mp = obj.mp;
const_cast&>(obj).mp = NULL;
}
return *this;
}
T* operator -> ()
{
return mp;
}
T& operator * ()
{
return *mp;
}
bool isNull()
{
return (mp == NULL);
}
T* get()
{
return mp;
}
~SmartPointer()
{
delete mp;
}
};
#endif
#include
#include
#include "SmartPointer.h"
using namespace std;
class Test
{
string m_name;
public:
Test(const char* name)
{
cout << "Hello, " << name << "." << endl;
m_name = name;
}
void print()
{
cout << "I'm " << m_name << "." << endl;
}
~Test()
{
cout << "Goodbye, " << m_name << "." << endl;
}
};
int main()
{
SmartPointer pt(new Test("D.T.Software"));
cout << "pt = " << pt.get() << endl;
pt->print();
cout << endl;
SmartPointer pt1(pt);
cout << "pt = " << pt.get() << endl;
cout << "pt1 = " << pt1.get() << endl;
pt1->print();
return 0;
}
小结:
智能指针 C++中自动内存管理的主要手段
智能指针在各种平台上都有不同的表现形式
智能指针能够尽可能的避开内存相关的问题
STL 和 Qt 中都提供了对智能指针的支持
需求的提出
在架构设计时,某些类在整个系统生命中最多只能有一个对象存在
如何定义一个类,使得这个类最多只能创建一个对象?
要控制类的对象数目,必须对外隐藏构造函数
思路:
将构造函数的访问属性设置为 private
定义 instance 并初始化为 NULL
当需要使用对象时,访问 instance 的值
空值:创建对象,并用 instance 标记
非空值:返回 instance 标记的对象
例 1 单例模式初探
#include
#include
using namespace std;
class SObject
{
static SObject* c_instance;
SObject(const SObject&);
SObject& operator= (const SObject&);
SObject()
{
}
public:
static SObject* GetInstance();
void print()
{
cout << "this = " << this << endl;
}
};
SObject* SObject::c_instance = NULL;
SObject* SObject::GetInstance()
{
if( c_instance == NULL )
{
c_instance = new SObject();
}
return c_instance;
}
int main()
{
SObject* s = SObject::GetInstance();
SObject* s1 = SObject::GetInstance();
SObject* s2 = SObject::GetInstance();
s->print();
s1->print();
s2->print();
return 0;
}
存在的问题
需要使用单例模式时:
必须定义静态成员变量 c_instance
必须定义静态成员函数 GetInstance()
解决方案
将单例模式相关的代码抽取出来,开发单例类模板。当需要单例类时,直接使用单例类
模板。
例 2 单例类模板
#ifndef _SINGLETON_H_
#define _SINGLETON_H_
template
< typename T >
class Singleton
{
static T* c_instance;
public:
static T* GetInstance();
};
template
< typename T >
T* Singleton<T>::c_instance = NULL;
template
< typename T >
T* Singleton<T>::GetInstance()
{
if( c_instance == NULL )
{
c_instance = new T();
}
return c_instance;
}
#endif
#include
#include
#include "Singleton.h"
using namespace std;
class SObject
{
friend class Singleton; // 当前类需要使用单例模式
SObject(const SObject&);
SObject& operator= (const SObject&);
SObject()
{
}
public:
void print()
{
cout << "this = " << this << endl;
}
};
int main()
{
SObject* s = Singleton::GetInstance();
SObject* s1 = Singleton::GetInstance();
SObject* s2 = Singleton::GetInstance();
s->print();
s1->print();
s2->print();
return 0;
}
小结:
单例模式是开发中最常用的设计模式之一
单例模式的应用使得一个类最多只有一个对象
可以将单例模式相关的代码抽象成类模板
需要使用单例模式的类直接使用单例类模板
异常的概念
程序在运行过程中可能产生异常
异常(Exception)与 bug 的区别
异常是程序运行时可预料的执行分支
bug 是程序的错误,是不被预期的运行方式
异常(Exception)和 bug 的对比
异常
运行时产生 0 的情况
需要打开的外部文件不存在
数组访问时越界
bug
使用野指针
堆数组使用结束后未释放
选择排序无法处理长度为 0 的数组
C 语言经典处理方式:if。。。else。。。
void func(…)
{
if(判断是否产生异常)
{
正常情况代码逻辑;
}
else
{
异常情况带啊逻辑;
}
}
例 1 除法操作异常处理
#include
#include
using namespace std;
double divide(double a, double b, int* valid)
{
const double delta = 0.000000000000001;
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
*valid = 1;
}
else
{
*valid = 0;
}
return ret;
}
int main(int argc, char *argv[])
{
int valid = 0;
double r = divide(1, 0, &valid);
if( valid )
{
cout << "r = " << r << endl;
}
else
{
cout << "Divided by zero..." << endl;
}
return 0;
}
缺陷
divide 函数 3 个参数,难以理解其用法
divide 函数调用后必须判断 valid 代表的结果
当 valid 为 true 时,运算结果正常
当 valid 为 false 时,运算过程出现异常
通过 setjmp()和 longjmp()进行优化
int setjmp(jmp_buf env)
将当前上下文保存在 jmp_buf 结构体中
void longjmp(jmp_buf env,int val)
从 jmp_buf 结构体中恢复 setjmp()保存的上下文
最终从 setjmp 函数用点返回,返回值为 val
例 2 除法操作异常处理优化
#include
#include
#include
using namespace std;
static jmp_buf env;
double divide(double a, double b)
{
const double delta = 0.000000000000001;
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
}
else
{
longjmp(env, 1);
}
return ret;
}
int main(int argc, char *argv[])
{
if( setjmp(env) == 0 )
{
double r = divide(1, 1);
cout << "r = " << r << endl;
}
else
{
cout << "Divided by zero..." << endl;
}
return 0;
}
缺陷
setjmp()和 longjmp()的引入
必然涉及到使用全局变量
暴力跳转导致代码可读性降低
本质还是 if…else…异常处理方式
c 语言中的经典异常处理方式会使得程序中逻辑中混入大量的处理异常的代码
正常逻辑代码和异常处理代码混合在一起,导致代码迅速膨胀,难以维护。。。
例 3 异常处理代码分析
#include
#include
using namespace std;
#define SUCCESS 0
#define INVALID_POINTER -1
#define INVALID_LENGTH -2
#define INVALID_PARAMETER -3
int MemSet(void* dest, unsigned int length, unsigned char v)
{
if( dest == NULL )
{
return INVALID_POINTER;
}
if( length < 4 )
{
return INVALID_LENGTH;
}
if( (v < 0) || (v > 9) )
{
return INVALID_PARAMETER;
}
unsigned char* p = (unsigned char*)dest;
for(int i=0; ireturn SUCCESS;
}
int main(int argc, char *argv[])
{
int ai[5];
int ret = MemSet(ai, sizeof(ai), 0);
if( ret == SUCCESS )
{
}
else if( ret == INVALID_POINTER )
{
}
else if( ret == INVALID_LENGTH )
{
}
else if( ret == INVALID_PARAMETER )
{
}
return ret;
}
c++中有没有更好的异常处理方式
小结:
程序中不可避免的会产生异常
异常是开发阶段就可以预见的运行时问题
c 语言中通过经典的 if…else…方式处理异常
c++中存在更好的异常处理方式
C++内置了异常处理的语法元素 try…catch…
try 语句处理正常代码逻辑
catch 语句处理异常情况
try 语句中的异常由对应的 catch 语句处理
try
{
double r=divide(1,0);
}
catch(…)
{
cout<<”Divided by zero…”<
#include
#include
using namespace std;
double divide(double a, double b)
{
const double delta = 0.000000000000001;
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
}
else
{
throw 0;
}
return ret;
}
int main(int argc, char *argv[])
{
try
{
double r = divide(1, 0);
cout << "r = " << r << endl;
}
catch(...)
{
cout << "Divided by zero..." << endl;
}
return 0;
}
同一个 try 语句可以跟上多个 catch 语句
catch 语句可以定义具体处理的异常类型
不同类型的异常由不同的 catch 语句负责处理
try 语句中可以抛出任何类型的异常
catch(…)用于处理所有类型的异常
任何异常都只能被捕获(catch)一次
异常处理的匹配规则
异常抛出后,自上而下严格匹配每一个 catch 语句处理的类型
try
{
throw 1;
}
catch(Type t1)
{
}
catch(Type t2)
{
}
catch(TypeN tn)
{
}
异常处理匹配时,不进行任何的类型转换。
例 2 异常类型匹配
#include
#include
using namespace std;
void Demo1()
{
try
{
throw 'c';
}
catch(char c)
{
cout << "catch(char c)" << endl;
}
catch(short c)
{
cout << "catch(short c)" << endl;
}
catch(double c)
{
cout << "catch(double c)" << endl;
}
catch(...)
{
cout << "catch(...)" << endl;
}
}
void Demo2()
{
throw string("D.T.Software");
}
int main(int argc, char *argv[])
{
Demo1();
try
{
Demo2();
}
catch(char* s)
{
cout << "catch(char *s)" << endl;
}
catch(const char* cs)
{
cout << "catch(const char *cs)" << endl;
}
catch(string ss)
{
cout << "catch(string ss)" << endl;
}
return 0;
}
小结:
C++中直接支持异常处理的概念
try…catch…是 C++中异常处理的专用语句
try 语句处理正常代码逻辑,catch 语句处理异常情况
同一个 try 语句可以跟上多个 catch 语句
异常处理必须严格匹配,不进行任何的类型转换
catch 语句块中可以抛出异常
try
{
func();
}
catch(int i)
{
throw i;
}
catch(…)
{
throw;
}
catch 中抛出的异常需要外层的 try…catch…捕获
为什么要在 catch 中重新抛出异常?
catch 中捕获的异常可以被重新解释后抛出
工程开发中使用这样的方式统一异常类型
例 1 异常的重新解释
#include
#include
using namespace std;
void Demo()
{
try
{
try
{
throw 'c';
}
catch(int i)
{
cout << "Inner: catch(int i)" << endl;
throw i;
}
catch(...)
{
cout << "Inner: catch(...)" << endl;
throw;
}
}
catch(...)
{
cout << "Outer: catch(...)" << endl;
}
}
/*
假设: 当前的函数式第三方库中的函数,因此,我们无法修改源代码
函数名: void func(int i)
抛出异常的类型: int
-1 ==》 参数异常
-2 ==》 运行异常
-3 ==》 超时异常
*/
void func(int i)
{
if( i < 0 )
{
throw -1;
}
if( i > 100 )
{
throw -2;
}
if( i == 11 )
{
throw -3;
}
cout << "Run func..." << endl;
}
void MyFunc(int i)
{
try
{
func(i);
}
catch(int i)
{
switch(i)
{
case -1:
throw "Invalid Parameter";
break;
case -2:
throw "Runtime Exception";
break;
case -3:
throw "Timeout Exception";
break;
}
}
}
int main(int argc, char *argv[])
{
// Demo();
try
{
MyFunc(11);
}
catch(const char* cs)
{
cout << "Exception Info: " << cs << endl;
}
return 0;
}
异常的类型可以是自定义类类型
对于类类型的匹配依旧是自上而下严格匹配
赋值兼容性原则在异常匹配中依然适用
一般而言
匹配子类异常的 catch 放在上部
匹配父类异常的 catch 放在下部
在工程中会定义一系列的异常类
每个类代表工程中可能出现的一种异常类型
代码复用时可能需要重解释不同的异常类
在定义 catch 语句块时推介使用引用作为参数
例 2 类类型的异常
#include
#include
using namespace std;
class Base
{
};
class Exception : public Base
{
int m_id;
string m_desc;
public:
Exception(int id, string desc)
{
m_id = id;
m_desc = desc;
}
int id() const
{
return m_id;
}
string description() const
{
return m_desc;
}
};
/*
假设: 当前的函数式第三方库中的函数,因此,我们无法修改源代码
函数名: void func(int i)
抛出异常的类型: int
-1 ==》 参数异常
-2 ==》 运行异常
-3 ==》 超时异常
*/
void func(int i)
{
if( i < 0 )
{
throw -1;
}
if( i > 100 )
{
throw -2;
}
if( i == 11 )
{
throw -3;
}
cout << "Run func..." << endl;
}
void MyFunc(int i)
{
try
{
func(i);
}
catch(int i)
{
switch(i)
{
case -1:
throw Exception(-1, "Invalid Parameter");
break;
case -2:
throw Exception(-2, "Runtime Exception");
break;
case -3:
throw Exception(-3, "Timeout Exception");
break;
}
}
}
int main(int argc, char *argv[])
{
try
{
MyFunc(11);
}
catch(const Exception& e)
{
cout << "Exception Info: " << endl;
cout << " ID: " << e.id() << endl;
cout << " Description: " << e.description() << endl;
}
catch(const Base& e)
{
cout << "catch(const Base& e)" << endl;
}
return 0;
}
C++标准库中提供了实用异常类族
标准库中的异常都是从 exception 类派生的
exception 类有两个主要的分支
logic_error
常用于程序中可避免逻辑错误
runtime_error
常用于程序中无法避免的恶性错误
例 3 标准库中的异常使用
#ifndef _ARRAY_H_
#define _ARRAY_H_
#include
using namespace std;
template
< typename T, int N >
class Array
{
T m_array[N];
public:
int length() const;
bool set(int index, T value);
bool get(int index, T& value);
T& operator[] (int index);
T operator[] (int index) const;
virtual ~Array();
};
template
< typename T, int N >
int Array::length() const
{
return N;
}
template
< typename T, int N >
bool Array::set(int index, T value)
{
bool ret = (0 <= index) && (index < N);
if( ret )
{
m_array[index] = value;
}
return ret;
}
template
< typename T, int N >
bool Array::get(int index, T& value)
{
bool ret = (0 <= index) && (index < N);
if( ret )
{
value = m_array[index];
}
return ret;
}
template
< typename T, int N >
T& Array::operator[] (int index)
{
if( (0 <= index) && (index < N) )
{
return m_array[index];
}
else
{
throw out_of_range("T& Array::operator[] (int index)" );
}
}
template
< typename T, int N >
T Array::operator[] (int index) const
{
if( (0 <= index) && (index < N) )
{
return m_array[index];
}
else
{
throw out_of_range("T Array::operator[] (int index) const" );
}
}
template
< typename T, int N >
Array::~Array()
{
}
#endif
#ifndef _HEAPARRAY_H_
#define _HEAPARRAY_H_
#include
using namespace std;
template
< typename T >
class HeapArray
{
private:
int m_length;
T* m_pointer;
HeapArray(int len);
HeapArray(const HeapArray& obj);
bool construct();
public:
static HeapArray* NewInstance(int length);
int length() const;
bool get(int index, T& value);
bool set(int index ,T value);
T& operator [] (int index);
T operator [] (int index) const;
HeapArray& self();
const HeapArray& self() const;
~HeapArray();
};
template
< typename T >
HeapArray::HeapArray(int len)
{
m_length = len;
}
template
< typename T >
bool HeapArray::construct()
{
m_pointer = new T[m_length];
return m_pointer != NULL;
}
template
< typename T >
HeapArray* HeapArray::NewInstance(int length)
{
HeapArray* ret = new HeapArray(length);
if( !(ret && ret->construct()) )
{
delete ret;
ret = 0;
}
return ret;
}
template
< typename T >
int HeapArray::length() const
{
return m_length;
}
template
< typename T >
bool HeapArray::get(int index, T& value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
value = m_pointer[index];
}
return ret;
}
template
< typename T >
bool HeapArray::set(int index, T value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
m_pointer[index] = value;
}
return ret;
}
template
< typename T >
T& HeapArray::operator [] (int index)
{
if( (0 <= index) && (index < length()) )
{
return m_pointer[index];
}
else
{
throw out_of_range("T& HeapArray::operator [] (int index)" );
}
}
template
< typename T >
T HeapArray::operator [] (int index) const
{
if( (0 <= index) && (index < length()) )
{
return m_pointer[index];
}
else
{
throw out_of_range("T HeapArray::operator [] (int index) const" );
}
}
template
< typename T >
HeapArray& HeapArray::self()
{
return *this;
}
template
< typename T >
const HeapArray& HeapArray::self() const
{
return *this;
}
template
< typename T >
HeapArray::~HeapArray()
{
delete[]m_pointer;
}
#endif
#include
#include
#include "Array.h"
#include "HeapArray.h"
using namespace std;
void TestArray()
{
Array<int, 5> a;
for(int i=0; ifor(int i=0; icout << a[i] << endl;
}
}
void TestHeapArray()
{
HeapArray<double>* pa = HeapArray<double>::NewInstance(5);
if( pa != NULL )
{
HeapArray<double>& array = pa->self();
for(int i=0; i<array.length(); i++)
{
array[i] = i;
}
for(int i=0; i<array.length(); i++)
{
cout << array[i] << endl;
}
}
delete pa;
}
int main(int argc, char *argv[])
{
try
{
TestArray();
cout << endl;
TestHeapArray();
}
catch(...)
{
cout << "Exception" << endl;
}
return 0;
}
小结:
catch 语句块中可以抛出异常
异常的类型可以是自定义类类型
赋值兼容性原则在异常匹配中依然适用
标准库中的异常都是从 exception 类派生的
在面向对象中可能出现下面的情况
基类指针指向子类对象
基类引用成为子类对象的别名
Base *p=new Derived();
Base r=*p;
静态类型-变量(对象)自身的类型
动态类型-指针(引用)所指向对象的实际类型
void test(Base *b)
{
//危险的转换方式
Derived *d=static_cast
#include
#include
using namespace std;
class Base
{
public:
virtual string type()
{
return "Base";
}
};
class Derived : public Base
{
public:
string type()
{
return "Derived";
}
void printf()
{
cout << "I'm a Derived." << endl;
}
};
class Child : public Base
{
public:
string type()
{
return "Child";
}
};
void test(Base* b)
{
/* 危险的转换方式 */
// Derived* d = static_cast(b);
if( b->type() == "Derived" )
{
Derived* d = static_cast(b);
d->printf();
}
// cout << dynamic_cast(b) << endl;
}
int main(int argc, char *argv[])
{
Base b;
Derived d;
Child c;
test(&b);
test(&d);
test(&c);
return 0;
}
多态解决方案的缺陷
必须从基类开始提供类型虚函数
所有的派生类都必须重写类型虚函数
每个派生类的类型名必须唯一
C++提供了 typeid 关键字用于获取类型信息
typeid 关键字返回对应参数的类型信息
typeid 返回一个 type_info 类对象
当 typeid 的参数为 NULL 时将抛出异常
typeid 关键字的使用
int i=0;
const type_info& tiv=typeid(i);
const type_info& tii=typeid(int);
cout<<(tiv==tii)<
#include
#include
#include
using namespace std;
class Base
{
public:
virtual ~Base()
{
}
};
class Derived : public Base
{
public:
void printf()
{
cout << "I'm a Derived." << endl;
}
};
void test(Base* b)
{
const type_info& tb = typeid(*b);
cout << tb.name() << endl;
}
int main(int argc, char *argv[])
{
int i = 0;
const type_info& tiv = typeid(i);
const type_info& tii = typeid(int);
cout << (tiv == tii) << endl;
Base b;
Derived d;
test(&b);
test(&d);
return 0;
}
小结:
C++中有静态类型和动态类型的概念
利用多态能够实现对象的动态类型识别
typeid 是专用于类型识别的关键字
typeid 能够返回对象的动态类型信息
编写程序判断一个变量是不是指针?
拾遗
C++中任然支持 C 语言中的可变参数函数
C++编译器的匹配优先级
1、重载函数
2、函数模板
3、可变函数
思路
将变量分为两类:指针 vs 非指针
编写函数:
指针变量调用时返回 true
非指针变量调用时返回 false
函数模板与可变函数的化学反应
template
bool IsPtr(T *v) //match pointer
{
return true;
}
bool IsPtr(…) //math non-pointer
{
return false;
}
例 1 指针判断
#include
#include
using namespace std;
class Test
{
public:
Test()
{
}
virtual ~Test()
{
}
};
template
<typename T>
char IsPtr(T* v) // match pointer
{
return 'd';
}
int IsPtr(...) // match non-pointer
{
return 0;
}
#define ISPTR(p) (sizeof(IsPtr(p)) == sizeof(char))
int main(int argc, char *argv[])
{
int i = 0;
int* p = &i;
cout << "p is a pointer: " << ISPTR(p) << endl; // true
cout << "i is a pointer: " << ISPTR(i) << endl; // false
Test t;
Test* pt = &t;
cout << "pt is a pointer: " << ISPTR(pt) << endl; // true
cout << "t is a pointer: " << ISPTR(t) << endl; // false
return 0;
}
存在的缺陷
变参函数无法解析对象参数,可能造成程序崩溃
进一步的挑战
如何让编译器精确匹配函数,但不进行实际的调用
如果构造函数中抛出异常会发生什么情况?
构造函数中抛出异常
构造过程立即停止
当前对象无法生成
析构函数不会被调用
对象所占用的空间立即收回
工程项目中的建议
不要在构造函数中抛出异常
当构造函数可能产生异常时,使用二阶构造模式
例 2 构造中的异常
#include
#include
using namespace std;
class Test
{
public:
Test()
{
cout << "Test()" << endl;
throw 0;
}
virtual ~Test()
{
cout << "~Test()" << endl;
}
};
int main(int argc, char *argv[])
{
Test* p = reinterpret_cast(1);
try
{
p = new Test();
}
catch(...)
{
cout << "Exception..." << endl;
}
cout << "p = " << p << endl;
return 0;
}
避免在析构函数中抛出异常
析构函数的异常将导致:
对象所使用的资源无法完全释放
小结:
C++中依然支持变参函数
变参函数无法很好的处理对象参数
利用函数模板和变参函数能够判断指针变量
构造函数和析构函数中不要抛出异常
下面的程序想要表达什么意思?
template
class Test
{
Test(T t){}
};
template
void func(T a[],int len)
{
}
历史上的原因
早期的 C++直接复用 class 关键字来定义模板
但是泛型编程针对的不只是类类型
class 关键字的复用使得代码出现二定义
typename 诞生的直诱因
自定义类类型内部的嵌套类型
不同类中的同一个标识符可能导致二义性
编译器无法辨识标识符究竟是什么
例 1 模板中的二义性
#include
#include
using namespace std;
template < class T >
class Test
{
public:
Test(T t)
{
cout << "t = " << t << endl;
}
};
template < class T >
void func(T a[], int len)
{
for(int i=0; i//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
int a = 0;
class Test_1
{
public:
static const int TS = 1;
};
class Test_2
{
public:
struct TS
{
int value;
};
};
template
< class T >
void test_class()
{
typename T::TS * a; // 1. 通过泛指类型 T 内部的数据类型 TS 定义指针变量 a (推荐的解读方式)
// 2. 使用泛指类型 T 内部的静态成员变量 TS 与全局变量 a 进行乘法操作
}
int main(int argc, char *argv[])
{
// test_class();
test_class();
return 0;
}
typename 的作用:
1、在模板定义中声明泛指类型
2、明确告诉编译器其后的标识符为类型
下面的程序想要表达什么意思?
int func(int i) try
{
return i;
}
catch(…)
{
return -1;
}
int func(int i,int j)
throw (int)
{
return i+j;
}
try…catch 用于分隔正常功能代码与异常处理代码
try…catch 可以直接将函数实现分隔为 2 部分
函数声明和定义时可以直接指定可能抛出的异常类型
异常声明成为函数一部分可以提高代码可读性
函数异常声明的注意事项
函数异常声明是一种与编译器之间的契约
函数声明异常后就只能抛出声明的异常
抛出其他异常将导致程序运行终止
可以直接通过异常声明定义无异常函数
例 2 新的异常写法
#include
#include
using namespace std;
int func(int i, int j) throw(int, char)
{
if( (0 < j) && (j < 10) )
{
return (i + j);
}
else
{
throw '0';
}
}
void test(int i) try
{
cout << "func(i, i) = " << func(i, i) << endl;
}
catch(int i)
{
cout << "Exception: " << i << endl;
}
catch(...)
{
cout << "Exception..." << endl;
}
int main(int argc, char *argv[])
{
test(5);
test(10);
return 0;
}
小结:
class 可以用来在模板中定义泛指类型(不推介)
typename 是可以消除模板中的二义性
try…catch 可以将函数体分成 2 部分
异常声明能够提供程序的可读性
统计对象中某个成员变量的访问次数
mutable 是为了突破 const 函数的限制而设计的
mutable 成员变量将永远处于可改变的状态
mutable 在实际的项目开发中被严禁滥用
mutable 的深入分析
mutable 成员变量破坏了只读对象的内部状态
const 成员函数保证只读对象的状态不变性
mutable 成员变量的出现无法保证状态不变性
例 1 成员变量的访问统计
#include
#include
using namespace std;
class Test
{
int m_value;
int * const m_pCount;
/* mutable int m_count; */
public:
Test(int value = 0) : m_pCount(new int(0))
{
m_value = value;
/* m_count = 0; */
}
int getValue() const
{
/* m_count++; */
*m_pCount = *m_pCount + 1;
return m_value;
}
void setValue(int value)
{
/* m_count++; */
*m_pCount = *m_pCount + 1;
m_value = value;
}
int getCount() const
{
/* return m_count; */
return *m_pCount;
}
~Test()
{
delete m_pCount;
}
};
int main(int argc, char *argv[])
{
Test t;
t.setValue(100);
cout << "t.m_value = " << t.getValue() << endl;
cout << "t.m_count = " << t.getCount() << endl;
const Test ct(200);
cout << "ct.m_value = " << ct.getValue() << endl;
cout << "ct.m_count = " << ct.getCount() << endl;
return 0;
}
new 关键字创建出来的对象位于什么地方?
new/delete 的本质是 C++预定义的操作符
C++对这个两个操作符做了严格的行为定义
new:
1、获取足够大的内存空间(默认为堆空间)
2、在获取的空间中调用构造函数创建对象
delete:
1、调用析构函数销毁对象
2、归还对象所占用的空间(默认为堆空间)
在 C++中能够重载 new/delete 操作符
全局重载(不推介)
局部重载(针对具体类进行重载)
重载 new/delete 的意义在于改变动态对象创建时的内存分配方式
new/delete 的重载方式
//static member function
void *operator new (unsigned int size)
{
void *ret =NULL;
//ret point to allocated member
return ret;
}
//static member function
void operator delete (void* p)
{
//free the memory which is pointed by p
}
例 2 静态存储区中创建动态对象
#include
#include
using namespace std;
class Test
{
static const unsigned int COUNT = 4;
static char c_buffer[];
static char c_map[];
int m_value;
public:
void* operator new (unsigned int size)
{
void* ret = NULL;
for(int i=0; iif( !c_map[i] )
{
c_map[i] = 1;
ret = c_buffer + i * sizeof(Test);
cout << "succeed to allocate memory: " << ret << endl;
break;
}
}
return ret;
}
void operator delete (void* p)
{
if( p != NULL )
{
char* mem = reinterpret_cast<char*>(p);
int index = (mem - c_buffer) / sizeof(Test);
int flag = (mem - c_buffer) % sizeof(Test);
if( (flag == 0) && (0 <= index) && (index < COUNT) )
{
c_map[index] = 0;
cout << "succeed to free memory: " << p << endl;
}
}
}
};
char Test::c_buffer[sizeof(Test) * Test::COUNT] = {0};
char Test::c_map[Test::COUNT] = {0};
int main(int argc, char *argv[])
{
cout << "===== Test Single Object =====" << endl;
Test* pt = new Test;
delete pt;
cout << "===== Test Object Array =====" << endl;
Test* pa[5] = {0};
for(int i=0; i<5; i++)
{
pa[i] = new Test;
cout << "pa[" << i << "] = " << pa[i] << endl;
}
for(int i=0; i<5; i++)
{
cout << "delete " << pa[i] << endl;
delete pa[i];
}
return 0;
}
如何在指定的地址上创建 C++对象?
解决方案
在类中重载 new/delete 操作符
在 new 的操作符重载函数中返回指定的地址
在 delete 操作符重载中标记对应的地址可用
例 3 自定义动态对象的存储空间
#include
#include
#include
using namespace std;
class Test
{
static unsigned int c_count;
static char* c_buffer;
static char* c_map;
int m_value;
public:
static bool SetMemorySource(char* memory, unsigned int size)
{
bool ret = false;
c_count = size / sizeof(Test);
ret = (c_count && (c_map = reinterpret_cast<char*>(calloc(c_count, sizeof(char)))));
if( ret )
{
c_buffer = memory;
}
else
{
free(c_map);
c_map = NULL;
c_buffer = NULL;
c_count = 0;
}
return ret;
}
void* operator new (unsigned int size)
{
void* ret = NULL;
if( c_count > 0 )
{
for(int i=0; iif( !c_map[i] )
{
c_map[i] = 1;
ret = c_buffer + i * sizeof(Test);
cout << "succeed to allocate memory: " << ret << endl;
break;
}
}
}
else
{
ret = malloc(size);
}
return ret;
}
void operator delete (void* p)
{
if( p != NULL )
{
if( c_count > 0 )
{
char* mem = reinterpret_cast<char*>(p);
int index = (mem - c_buffer) / sizeof(Test);
int flag = (mem - c_buffer) % sizeof(Test);
if( (flag == 0) && (0 <= index) && (index < c_count) )
{
c_map[index] = 0;
cout << "succeed to free memory: " << p << endl;
}
}
else
{
free(p);
}
}
}
};
unsigned int Test::c_count = 0;
char* Test::c_buffer = NULL;
char* Test::c_map = NULL;
int main(int argc, char *argv[])
{
char buffer[12] = {0};
Test::SetMemorySource(buffer, sizeof(buffer));
cout << "===== Test Single Object =====" << endl;
Test* pt = new Test;
delete pt;
cout << "===== Test Object Array =====" << endl;
Test* pa[5] = {0};
for(int i=0; i<5; i++)
{
pa[i] = new Test;
cout << "pa[" << i << "] = " << pa[i] << endl;
}
for(int i=0; i<5; i++)
{
cout << "delete " << pa[i] << endl;
delete pa[i];
}
return 0;
}
new[]/delete[]与 new/delete 完全不同
动态对象数组创建通过 new[]完成
动态对象数组的销毁通过 delete[]完成
new[]/delete[]能够被重载,进而改变内存管理方式
new[]/delete[]的重载方式
//static member function
void *operator new[] (unsigned int size)
{
return malloc(size);
}
//static member function
void operator delete[] (void* p)
{
free (p);
}
注意事项
new[]实际需要返回的内存空间可能比期望的要多
对象数组占用的内存中需要保存数组信息
数组信息用于确定构造和析构函数的调用次数
例 4 动态数组的内存管理
#include
#include
#include
using namespace std;
class Test
{
int m_value;
public:
Test()
{
m_value = 0;
}
~Test()
{
}
void* operator new (unsigned int size)
{
cout << "operator new: " << size << endl;
return malloc(size);
}
void operator delete (void* p)
{
cout << "operator delete: " << p << endl;
free(p);
}
void* operator new[] (unsigned int size)
{
cout << "operator new[]: " << size << endl;
return malloc(size);
}
void operator delete[] (void* p)
{
cout << "operator delete[]: " << p << endl;
free(p);
}
};
int main(int argc, char *argv[])
{
Test* pt = NULL;
pt = new Test;
delete pt;
pt = new Test[5];
delete[] pt;
return 0;
}
小结:
new/delete 的本质为操作符
可以通过全局函数重载 new/delete(不推介)
可以针对具体的类重载 new/delete
new[]/delete[]与 new/delete 完全不同
new[]/delete[]也是可以被重载的操作符
new[]返回的内存空间可能比期望的要多
第 70 课 展望未来的学习之路
该课程学习的是“经典”C++语言
“经典”指的是什么?
C++98/03 标准在实际工程中的常用特性
大多数企业的产品开发需要使用的 C++技能
C++语言的学习需要重点在于以下几个方面
C 语言到 C++的改进有哪些?
面向对象的核心是什么?
操作符重载的本质是什么?
模板的核心意义是什么?
异常处理的使用方式是什么?
第 71 课 异常处理深度解析
问题:
如果在 main 函数中抛出异常会发生什么?
如果不处理,最后会传到哪里?
下面的代码输出什么?
class Test
{
public:
Test()
{
cout<<”Test()”;
cout<
#include
using namespace std;
class Test
{
public:
Test()
{
cout << "Test()";
cout << endl;
}
~Test()
{
cout << "~Test()";
cout << endl;
}
};
int main()
{
static Test t;
throw 1;
return 0;
}
如果异常无法被处理,terminate()结束函数会被自动调用
默认情况下,terminate()调用库函数 abort()终止程序
abort()函数使得程序执行异常而立即退出
C++支持替换默认的 terminate()函数实现
terminate()函数的替换
自定义一个无返回值五参数的函数
不能抛出任何异常
必须以某种方式结束当前程序
调用 set_terminate()设置自定义的结束函数
参数类型为 void(*)()
返回值为默认 terminate()函数入口地址
例 2 自定义结束函数
#include
#include
#include
using namespace std;
void my_terminate()
{
cout << "void my_terminate()" << endl;
exit(1);
}
class Test
{
public:
Test()
{
cout << "Test()";
cout << endl;
}
~Test()
{
cout << "~Test()";
cout << endl;
}
};
int main()
{
set_terminate(my_terminate);
static Test t;
throw 1;
return 0;
}
面试题
如果析构函数中抛出异常会发生什么情况?
例 3 析构函数抛出异常
#include
#include
#include
using namespace std;
void my_terminate()
{
cout << "void my_terminate()" << endl;
// exit(1);
abort();
}
class Test
{
public:
Test()
{
cout << "Test()";
cout << endl;
}
~Test()
{
cout << "~Test()";
cout << endl;
throw 2;
}
};
int main()
{
set_terminate(my_terminate);
static Test t;
throw 1;
return 0;
}
小结:
如果异常没有被处理,最后 terminate()结束整个程序
terminate()是整个程序释放系统资源的最后机会
结束函数可以自定义,但不能继续抛出异常
析构函数中不能抛出异常,可能导致 terminate()多次调用
第 72 课 函数的异常规格说明
问题:
如何判断一个函数是否会抛出异常,以及抛出哪些异常?
C++提供语法用于声明函数所抛出的异常
异常声明作为函数声明的修饰符,写在参数列表后面
//可能抛出任何异常
void func1();
//只能抛出的异常类型:char 和 int
void func2() throw(char,int);
//不抛出任何异常
void func3() throw();
异常规格说明的意义
提示函数调用者必须做好异常处理的准备
提示函数的维护者不要抛出其他异常
异常规则说明是函数接口的一部分
问题:
如果抛出的异常不在声明列表中,会发生什么?
下面的代码输出什么?
void func() throw(int)
{
cout<<”func()”;
cout<
#include
using namespace std;
void func() throw(int)
{
cout << "func()";
cout << endl;
throw 'c';
}
int main()
{
try
{
func();
}
catch(int)
{
cout << "catch(int)";
cout << endl;
}
catch(char)
{
cout << "catch(char)";
cout << endl;
}
return 0;
}
函数抛出的异常不在规格说明中,全局 unexpected()被调用
默认的 unexpected()函数会调用的 terminate()函数
可以自定义函数替换默认的 unexpected()函数实现
注意:不是所有的 C++编译器都支持这个标准行为
unexpected()函数的替换
自定义一个无返回值无参数的函数
能够再次抛出异常
当异常符合触发函数的异常规格说明时,恢复程序执行
否则,调用全局 terminate()函数结束程序
调用 set_unexpected()设置自定义的异常函数
参数类型为 void(*)()
返回值为默认的 unexpected()函数入口地址
例 2 自定义 unexpected()函数
#include
#include
#include
using namespace std;
void my_unexpected()
{
cout << "void my_unexpected()" << endl;
// exit(1);
throw 1;
}
void func() throw(int)
{
cout << "func()";
cout << endl;
throw 'c';
}
int main()
{
set_unexpected(my_unexpected);
try
{
func();
}
catch(int)
{
cout << "catch(int)";
cout << endl;
}
catch(char)
{
cout << "catch(char)";
cout << endl;
}
return 0;
}
小结:
C++中的函数可以声明异常规格说明
异常规格说明可以看作接口的一部分
函数抛出的异常不在规格说明中,unexpected()被调用
unexpected()中能够再次抛出异常
异常能够匹配,恢复程序的执行
否则,调用 terminate()结束程序
第 73 课 动态内存申请的结果
问题:
动态内存申请一定成功吗?
常见的动态内存分配代码
C 代码:
int p=(int )malloc(10*sizeof(int));
if(p!=NULL)
{
//…
}
C++代码:
int *p=new int[10];
if(p!=NULL)
{
//….
}
必须知道的事实!
malloc 函数申请失败时返回 NULL
new 关键字申请失败时(根据编译器的不同)
返回 NULL
抛出 std::bad_alloc 异常
问题:
new 语句中的异常是怎么抛出来的?
new 关键字在 C++规范中的标准行为
在堆空间申请足够大的内存
成功:
在获取的空间中调用构造函数创建的对象
返回对象的地址
失败:
抛出 std::bad_alloc 异常
new 关键字在 C++规范中的标准行为
new 在分配内存时
如果空间不足,会调用全局的 new_handler()函数
new_handler()函数中抛出 std::bad_alloc 异常
可以自定义 new_hand()函数
处理默认的 new 内存分配失败的情况
new_hand()的定义和使用
void my_nhandler()
{
cout<<”No enough memory”;
cout<
#include
#include
#include
#include
using namespace std;
class Test
{
int m_value;
public:
Test()
{
cout << "Test()" << endl;
m_value = 0;
}
~Test()
{
cout << "~Test()" << endl;
}
void* operator new (unsigned int size) throw()
{
cout << "operator new: " << size << endl;
// return malloc(size);
return NULL;
}
void operator delete (void* p)
{
cout << "operator delete: " << p << endl;
free(p);
}
void* operator new[] (unsigned int size) throw()
{
cout << "operator new[]: " << size << endl;
// return malloc(size);
return NULL;
}
void operator delete[] (void* p)
{
cout << "operator delete[]: " << p << endl;
free(p);
}
};
void my_new_handler()
{
cout << "void my_new_handler()" << endl;
}
void ex_func_1()
{
new_handler func = set_new_handler(my_new_handler);
try
{
cout << "func = " << func << endl;
if( func )
{
func();
}
}
catch(const bad_alloc&)
{
cout << "catch(const bad_alloc&)" << endl;
}
}
void ex_func_2()
{
Test* pt = new Test();
cout << "pt = " << pt << endl;
delete pt;
pt = new Test[5];
cout << "pt = " << pt << endl;
delete[] pt;
}
void ex_func_3()
{
int* p = new(nothrow) int[10];
// ... ...
delete[] p;
int bb[2] = {0};
struct ST
{
int x;
int y;
};
ST* pt = new(bb) ST();
pt->x = 1;
pt->y = 2;
cout << bb[0] << endl;
cout << bb[1] << endl;
pt->~ST();
}
int main(int argc, char *argv[])
{
// ex_func_1();
// ex_func_2();
// ex_func_3();
return 0;
}
/***
*new.cxx - defines C++ new routine
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
* Defines C++ new routine.
*
*******************************************************************************/
#ifdef _SYSCRT
#include
#include
#include
#include
#include
#include
#include
#include
void * operator new( size_t cb )
{
void *res;
for (;;) {
// allocate memory block
res = _heap_alloc(cb);
// if successful allocation, return pointer to memory
if (res)
break;
// call installed new handler
if (!_callnewh(cb))
break;
// new handler was successful -- try to allocate again
}
RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0));
return res;
}
#else /* _SYSCRT */
#include
#include
_C_LIB_DECL
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{ // report no memory
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/*
* Copyright (c) 1992-2002 by P.J. Plauger. ALL RIGHTS RESERVED.
* Consult your license regarding permissions and restrictions.
V3.13:0009 */
#endif /* _SYSCRT */
实验结论
不是所有的编译器都遵循 C++标准规范
编译器可能重定义 new 的实现,并在实现中抛出 bad_alloc 异常
编译器的默认实现中,可能没有设置全局的 new_handler()函数
对于移植性要求较高的代码,需要考虑 new 的具体细节
小结:
不同的编译器在动态内存分配上的实现细节不同
malloc 函数在内存申请失败时 NULL 值
new 和关键字在内存申请失败时
可能返回 NULL 值
可能抛出 bad_alloc 异常