摘自侯捷老师的《面向对象高级开发(上下两部曲)》
谈谈const
const常规用法
-
如果关键字
const
出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。char greeting[] = "Hello"; char* p= greeting; \\ non-const pointer,non-const data const char* p =greeting; \\ non-const pointer,const data char* const p = greeting; \\ const pointer,non-const data const char* const p = greeting; \\const pointer,const data
-
STL迭代器系以指针为根据塑模出来,所以迭代器的作用就像个
T
指针。声明迭代器为const就像声明指针为const
一样(即声明一个T const
指针),表示这个迭代器不得指向不同的东西,但它所指的东西的值是可以改动的。如果希望迭代器所指的东西不可被改动(即希望STL模拟一个const T*
指针),你需要的是const_iterator
:
vector<int>vec;
const vector<int>::iterator iter = vec.begin(); //iter的作用像个T* const
*iter =10; //没问题,改变iter所指物
++iter; // 错误,iter是const
vector<int>:: const_iterator cIter = vec.begin(); //cIter的作用像个const T*
*cIter =10; //错误,*cIter是const
++cIter; //没问题,改变cIter
const
修饰函数时
-
const
在函数名前面的时候表示修饰函数返回值 -
const
在函数名后面的时候表示修饰成员函数,该成员函数只会读取但不改变class
的data
。const object
(data members不得变动)non-const object
(data members可变动)const member function
(保证不更改data members)√ √ non-const member functions
(不保证data members不变)× √ Tips:当成员函数的
const
和non-const
版本同时存在,const object
只会调用const
版本,non-const object
只会调用non-const
版本。const Sring str("hello world!"); str.print(); //如果当初设计String::print()时未声明const,那么上行经由const object调用non-const member function会出错。此非吾所愿。
Tips:也就说在声明函数的时候,只要函数没有改变数据,就加上const。
转换函数
- 第一种
class Fraction
{
public:
Fraction(int num, int den =1): m_numerator(num), m_denominator(den) { }
operation double() const {
return (double)(m_numerator / m_denominator)
}
private:
int m_numerator;
int m_denominator;
}
//用法
Fraction f(3, 5);
double d = 4 + f; //调用operator double()将f转为0.6
- 第二种:non-explicit-one-argument ctor
class Fraction
{
public:
Fraction(int num, int den=1): m_numerator(num), m_denominator(den){ }
Fraction operator+(const Fraction& f){
return Fraction(......); //操作符重载返回Fraction
}
private:
int m_numerator;
int m_denominator;
}
//用法
Fraction f(3,5);
Fraction d2=f+4; //调用non-explict ctor 将4转为Fraction(4,1),然后调用operator+
C++子类的构造函数后面加:冒号的作用
在C++类的构造函数中经常会看到如下格式的写法:
MyWindow::MyWindow(QWidget* parent , Qt::WindowFlags flag) : QMainWindow(parent,flag)
上述语句中单冒号(:)的作用是表示后面是初始化列表,一般有三种使用场景。
- 对父类进行初始化
调用格式为“子类构造函数 : 父类构造函数”,如下,其中QMainWindow
是MyWindow
的父类:
MyWindow::MyWindow(QWidget* parent , Qt::WindowFlags flag) : QMainWindow(parent,flag)
- 对类成员进行初始化
调用格式为“构造函数 : A(初始值),B(初始值),C(初始值)……”,如下,其中A、B、C分别是类的成员变量:
class rectangle //头文件中类定义
{
public:
rectangle( int pointX, int pointY, int Width, int Length );
private:
CPoint m_point;
int m_Width;
int m_Length;
};
rectangle::rectangle(int pointX, int pointY, int Width, int Length) : m_point(pointX,pointY),m_Width(Width),m_Length(Length)//源文件中构造函数实现
{
todo......
}
当然,上面构造函数的实现与下面的写法等价
rectangle::rectangle(int pointX, int pointY, int Width, int Length)//源文件中构造函数实现
{
m_point.X = pointX;
m_point.Y = pointY;
m_Width = Width;
m_Length = Length;
todo......
}
- 对类的const成员变量进行初始化
由于const成员变量的值无法在构造函数内部初始化,因此只能在变量定义时赋值或使用初始化列表赋值。
对于2、3中的应用场景,有以下两点说明:
1、构造函数列表初始化执行顺序与成员变量在类中声明顺序相同,与初始化列表中语句书写先后无关。
2、相对于在构造函数中赋值,初始化列表执行效率更高。
point-like classes
-
关于智能指针
template<class T> class shared_ptr { public: T& operator*() const { return *px; } T* operator->() const { return px; } shared_ptr(T* p): px(p) { } private: T* px; long* pn; ....... }; struct Foo { ....... void method(void) }; //用法 shared_ptr<Foo> sp(new Foo); Foo f(*sp); sp->method(); // == px->method();
-
关于迭代器
template<class T, class Ref, class Ptr> struct __list_iterator { ....... typedef __list_node<T>* link_type; link_type node; reference operator*() const { return (*node).data; } pointer operator->() const { return &(operator*()); } ....... } template <class T> struct __list_node { void* prev; void* next; T data; } //用法 list<Foo>::iterator ite; ... *ite; //获得一个Foo Object; ite->method(); //意思是调用Foo::method(); //相当于(*ite).method(); //相当于(&(*ite))->method();
namespace经验谈
using namespace std;
//----------------------------------------------
#include <iostream>
#include <memory> //shared_ptr
namespace jj01
{
void test_member_template() { ...... }
} //namespace
//----------------------------------------------
#include <iostream>
#include <list>
namespace jj02
{
template<typename T>
using Lst = list<T, allocator<T>>;
void test_template_template_param() { ....... }
} //namespace
//---------------------------------------------
//用法
int main(int argc, char** argv)
{
jj01::test_member_template();
jj02::test_template_template_param();
}
class template,类模板
类是模板
//类模板
template<typename T>
class complex
{
public:
complex (T r = 0, T i = 0): re(r), im(i) { }
complex& operator += (const complex&);
T real () const { return re; }
T imag () const { return im; }
private:
T re, im;
friend complex& __dopal (complex*, const complex&);
}
//使用
complex<double> c1(2.5, 1.5);
complex<int> c2(2,6);
function template,函数模板
函数是模板
stone r1(2,3), r2(3,3), r3;
r3 = min(r1, r2);
//函数模板
template <class T>
inline
const T& min(const T& a,const T& b){
return b < a ? b:a;
}
class stone
{
public:
stone(int w, int h, int we):_w(w), _h(h), _weight(we){ }
bool operator< (const stone& rhs) const {return _weight < rhs>_weight; }
private:
int _w, _h, _weight;
}
member template,成员模板
模板本身是一个类的成员
template <class T1, class T2>
struct pair{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair():first(T1()), second(T2()) { }
pair(const T1& a, const T2& b):first(a), second(b) { }
//成员模板
template <class U1, class U2>
pair(const pair<U1, U2>& p):first(p.first), second(p.second) { }
}
specialization,模板特化
与模板泛化相反。模板泛化是使用的时候再定义具体类型。而模板特化是使用之前就定义具体类型。
//模板泛化
template <class Key>
struct hash { };
//模板特化
template<>
struct hash<char>{
size_t operator() (char x) const { return x; }
};
template<>
struct hash<int>{
size_t operator() (int x) const { return x; }
};
template<>
struct hash<long>{
size_t operator() (long x) const { return x; }
};
//使用
count << hash<long>() (1000);
patial specialization,模板偏特化
-
个数的偏
原来的泛化模板占用1个字节去表示占一位的bool,浪费内存。所以使用模板偏特化
template<typename T, typename Alloc=....> class vector { .... }; //将typename T 与 bool绑定 template<typename Alloc=...> class vector<bool, Alloc> { ... }
-
范围的偏
template <typename T> class C { ... }; template <typename T> class C<T*> { ... }; //使用 C<string> obj1; C<string*> obj2;
template template parameter,模板模板参数
-
例1
template<typename T, template <typename T> class Conrtainer<T> > class XCls { private: Container<T> c; public: ....... };
template<typename T> using Lst = list<T, allocate<T>>;
XCls<string, list> mylst1;//error,list作为容器,可能不止string这一个模板参数。 XCls<string, Lst> mylst2;
-
例2(SmartPtr只有一个模板参数)
template<typename T, template <typename T> class SmartPtr<T> > class XCls { private: SmartPtr<T> sp; public: XCls(): sp(new T) { } };
XCls<string, shared_ptr> p1; XCls<string, unique_ptr> p2;//error XCls<string, weak_ptr> p3;//error XCls<string, auto_ptr> p4;
variadic templates(since C++11)
void print()
{
}
template<typename T, typename... Types>
void print(const T& firstArg, const Type&... args)
{
cout << firstArg << endl;
cout << sizeof...(args) << endl;
print(args...);
}
//使用
print(7.5, "hello", bitset<16>(377), 42);
//第一次打印时,7.5是firstArg,其他是args;第二次打印时,"hello"是firstArg,其他是args;以此类推,最后一次分为42和空包。
...
就是一个所谓的pack
(包)
用于template parameters
,就是template parameters pack
(模板参数包)
用于function parameters types
,就是function parameters types pack
(函数参数类型包)
用于function parameters
,就是function parameters pack
(函数参数包)
auto(since C++11)
list<string> c;
...
list<string>::iterator ite;
ite = find(c.begin(), c.end(), target);
list<string> c;
...
auto ite = find(c.begin(), c.end(), target);
list<string> c;
...
auto ite;
ite = find(c.begin(), c.end(), target);//error,使用auto时,等号右边一定要有值。
ranged-base for(since C++11)
for (decl : coll) {
statement;
}
for (int i : {2, 3, 5, 7, 9, 13, 17, 19 }){
cout << i << endl;
}
vector<double> vec;
...
for (auto elem : vec){//elem数值改变不会改变vec中的元素
cout << elem << endl;
}
for (auto& elem : vec){//elem数值改变会改变vec中的元素,因为elem代表vec中元素
elem *= 3;
}
reference
int x = 0;
int* p = &x; // p is a pointer to x/变量p point to int,初始化为x的地址
int& r = x; // r is a reference to x/变量r reference to int,初始化为x,代表x
int x2 = 5;
r = x2; //r不能重新代表其他物体。现在r,x都是5
int& r2 = r; //现在r2是5(r2代表r;亦相当于代表x)
例子(object和其reference的大小相同,地址也相同(全都是假象)):
typedef struct Stag { int a,b,c,d} S;
int main(){
double x = 0;
double* p = &x; //p指向x,p的值是x的地址
double& r = x; //r代表x,现在r,x都是0
cout << sizeof(x) << endl; //8
cout << sizeof(p) << endl; //4
cout << sizeof(r) << endl; //8
cout << p << endl; //0065FDFC
cout << *p << endl; //0
cout << x << endl; //0
cout << r << endl; //0
cout << &x << endl; //0065FDFC
cout << &r << endl; //0065FDFC
S s;
S& rs = s;
cout << sizeof(s) << endl; //16
cout << sizeof(rs) << endl; //16
cout << &s << endl; //0065FDE8
cout << &rs << endl; //0065FD68
}
reference的常见用途
void func1(Cls* pobj) { pobj->xxx(); }
void func2(Cls obj) { obj.xxx(); }
void func3(Cls& obj) { obj.xxx(); } //传值和传引用的被调用端写法相同,很好
...
Cls obj;
func1(&obj); //接口调用不同,困扰
func2(obj);
func3(obj); //调用端接口相同,很好
reference通常不用于声明变量,而用于参数类型(parameters type)和返回类型(return type)的描述。以下被视为same signature
(所以二者不能同时存在):
double imag(const double& im) {...}
double imag(const double im) {...} //Ambiguity
Inheritance(继承)关系下的构造和析构
举个例子,猪(Derived)是哺乳动物(Base)。
base class
的dtor
必须是virtual
,否则会出现undefined behavior
。
构造由内而外
Derived的构造函数首先调用Base
的default
构造函数,然后才执行自己。
Derived::Derived(...) : Base() { ... };
析构由外而内
Derived的析构函数首先执行自己,然后才调用Base的析构函数。
Derived::~Derived(...) {... ~Base() };
Composition(复合)关系下的构造和析构
Container
包含Component
。
构造由内而外
Container
的构造函数首先调用Componebt
的default
构造函数,然后才执行自己。
Container::Container(...) : Component() { ... };
析构由外而内
Container的析构函数首先执行自己,然后才调用Component的析构函数。
Container::~Container(...) { ... ~Component() };
Inheritance+Conposition关系下的构造和析构
构造由内而外
Derived的构造函数首先调用Base
的default
构造函数,然后调用Component
的default
构造函数,然后才执行自己。
Derived::Derived(...) : Base(),Component() { ... };
析构由外而内
Derived
的析构函数首先执行自己,然后调用Component
的析构函数,然后调用Base
的析构函数。
Derived::~Derived(...) { ... ~Compoent(),Base() };
对象模型(Object Model):关于vptr和vtbl
class A{
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};
class B{
public:
virtual void vfunc1();
void func2();
private:
int m_data3;
}
class C{
public:
virtual void vfunc1();
void func2();
private:
int m_data1, m_data4;
}
//总结
1.父类中虚函数都会被子类继承,子类可以重写父类中的虚函数。即子类要想重写父类的函数,需要在父类中的哪个函数之前加上关键字virtual。
2.子类继承的是父类中函数的调用权,而不是内存。
对象模型(Object Model):关于Dynamic Binding
动态绑定:
将汇编语言call
及以上的代码翻译成C++语言,如下:
(*(p->vptr)[n])(p)
或
(*p->vptr[n])(p)
动态绑定需要满足三个条件:
- 通过指针
- 虚函数
- 向上转型