前言
术语
声名:告诉编译器某个东西的名称和类型,但略去细节。
定义:提供编译器一些声名所遗漏的细节。
初始化:“给对象初始”的过程。
class GamePlayer
{
private:
enum {NumTurns = 5};
int scores[NumTurns];
}
若编译器不允许“int-class 初始值设定”
class TextBlock
{
public:
TextBlock(string s):text(s) {}
const char& operator[](std::size_t position) const
{
return text[position];
}
char& operator[](std::size_t position)
{
return text[position];
}
private:
std::string text;
};
int main()
{
TextBlock tb("Hello");
cout << tb[0] << endl; //调用non-const TextBlock::operator[]
const TextBlock ctb("WORLD");
cout << ctb[0] << endl; //调用const TextBlock::operator[]
tb[0] = '5'; //✔
ctb[0] = '5'; //❌
}
static_cast, const_cast
class PhoneNumber {...};
class ABEntry {
public:
ABEntry(const std::string&name);
private:
std::string theName;
};
ABEntry::ABEntry(const std::string&name):theNmae(name) //这叫初始化
{
theName = name; //着叫赋值,非初始化
}
默认声名:
一个默认构造函数
copy构造函数
copy assignment操作符
析构函数
just like this
class Empty
{
public:
Empty() {}
Empty(const Empty& rhs) {}
Empty& operator=(const Empty& rhs) {}
~Empty() {}
};
Empty e1; //default 构造函数
//析构函数
Empty e2(e1); //copy构造函数
e2 = e1; //copy assignment 操作符
class Empty
{
public:
Empty() = delete;
Empty(const Empty&) = delete;
Empty& operator=(const Empty&) = delete;
~Empty() = delete;
};
class SpecialString: public std::string { // std::string有个 non-virtual析构函数
...
};
SpecialString* pss = new SpecialString("sdf");
std::string *ps;
ps = pss; //静态类型与动态类型不一致
delete ps; //❌
构造函数内的virtual函数还不是virtual的
class Widget{
public:
...
Widget& oeprator=(const Widget& rhs){
...
return *this;
}
};
隐式的自赋值
a[i] = a[j]; *px = *py
Widget& operator=(const Widget& rhs)
{
/* 如果new Bitmap抛出异常。它不是异常安全的 */
if (this = &rhs) return *this;
delete pb; //先把原来的对象删除
pb = new Bitmap(*rhs.pb); //再new一个
return *this;
/* 异常安全,但自赋值不高效 */
Bitmap* pOrig = pb;
pb = new Bitmap(rhs->pb);
delete pOrig;
return *this;
Bitmap* tmp = new Bitmap(rhs->pb);
delete pb ;
pb = tmp;
return *this
/* x */
Widget tmp(rhs);
swap(tmp);
return *this;
}
Widget& Widget::operator=(Widget rhs)
{
swap(rhs);
return *this;
}
使用智能指针管理内存
注意:
shared_ptr iptr(new int[5]); //不报错,但默认还是用delete 释放内存,会导致内存泄露
class Investment{
public:
bool isTaxFree() const;
...
};
Investment* createInvestment(); //假定返回一个堆内存指针
std::shared_ptr pInv(createInvestment());
int daysHeld(const Investment* pi);
daysHeld(pInv.get());
pInv->isTaxFree();
(*pInv).isTaxFree();
std::string* stringArray = new std::string[100];
...
delete stringArray; //❌ , delete [] stringArray
/* 当调用new时 1.通过operator new函数分配内存 2.调用构造函数
delete 与new顺序相反
*/
int priority();
void processWidget(std::shared_ptr pw, int priority);
考虑
processWidget(std::shared_ptr(new Widget), priority());
其执行顺序可能为:
1. new Widget
2. priority()
3. shared_ptr构造函数
若priority()发生异常,则会造成内存泄露
auto pw = std::shared_ptr(new Widget);
processWidget(pw, priority());
struct Day{
explicit Day(int d): val(d) {}
int val;
};
struct Month{
explicit Month(int m):val(m) {}
int val;
};
struct Year{
explicit Year(int y):val(y) {}
int val;
};
class Date{
public:
Date(const Month&, const Day&, const Year&);
};
尽量与内置类型的行为相同,a=b, a*b = c报错等
shared_ptr要比原始指针大且慢,然而其“降低客户错误”的成效却是较为显著和方便的
设计class时需要考虑:
class Person{
public:
Person();
virtual ~Person();
...
private:
std::string name;
std::string address;
};
class Student: public Person{
public:
Student();
~Student();
...
private:
std::string schoolName;
std::string schoolAddress;
};
论点:
如果改变public,所有设计到public的接口倒要改变,如果改变protected,所有用到protected的子类都要变。从封装的角度看,只有private(提供封装)和其它(步提供封装)。
class WebBrowser
{
public:
...
void clearCacke();
void clearHistory();
void removeCookies();
...
# 一:member函数
void clearEverything();
};
#二:non-member函数
void clearBrowser(WebBrowser &wb){
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
// 头文件“webbrowser.h”这个头文件针对class WebBrowser自身及WebBrowser核心机能
namespace WebBrowserStuff{
class WebBrowser{...};
... //核心机能,如几乎所有客户都需要的 non-member函数
}
// 头文件"webbrowserbookmarks.h"
namespace WebBrowserStuff{
... //与书签相关的便利函数
}
// 头文件“webbrowsercookies.h”
namespace WebBrowserStuff{
... //与cookie相关的便利函数
}
namespace可以跨头文件,而class必须定义在一个头文件中
将多个“提供便利的函数”放在多个头文件内但隶属同一个namespace,用户可以轻松扩展这一组“提供便利的函数”。如,WebBrowser客户要添加些与影响下载相关的“提供便利的函数”,只需要再WebBrowserStuff命名空间内建立一个头文件,内含那些函数的声名即可。(若在WebBrowser命名空间中声名“提供便利的函数”,那么定义放在哪?)
swap多少与异常安全性编程(exception-safe programming)相关
默认swap行为:(如果ab都在堆中该如何处理?a,b都为内置类型,先不考虑堆的情况)
namespace std {
template void swap(T& a, T& b)
{
T temp(a); //a,b都为内置类型
a = b;
b = temp;
}
}
提供public swap成员
将std::swap特例化
class WidgetImpl
{
public:
...
private:
int a, b, c; //可能有许多数据
std::vector v; //意味复制时间很长
...
};
class Widget
{
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs)
{
...
*pImpl = *(rhs.pImpl);
...
}
void swap(Widget& other)
{
using std::swap;
swap(pImpl, other.pImpl);
}
private:
WidgetImpl* pImpl;
};
namespace std{
template<> void swap(Widget& a, Widget& b)
{
a.swap(b);
}
}
std的内容完全由C++标准委员会决定,标准禁止我们膨胀那些已经声名好的东西。即,不要添加任何新东西到std里头。
所以:
namespace WidgetStuff{
...
template class Widget {...};
...
// non-member swap function, 这里不属于std命名空间
template void swap(Widget& a, Widget& b)
{ a.swap(b); }
}
template void doSomething(T& obj1, T& obj2)
{
using std::swap; // 令std::swap在此函数内可用
...
swap(obj1, obj2); //为T性对象调用最佳swap版本
...
}
default swap, member swaps, non-member swaps, std::swap特例化版本
remember
通过default构造函数构造出一个对象然后对它赋值比直接在构造时指定初值效率差。
以下哪个好?
//方法A
Widget w;
for (int i=0; i//方法B
for (int i=0; i
Widget w(取决于i的某个值);
…
}
C风格的转型动作:
(T) expression
函数风格的转型动作:
T(expression)
C++四种新式转型:
const_cast
将对象的常量属性移除。它也是唯一由此功能的C+±style转型操作符。
dynamic_cast
将父类转换为子类
用于“安全向下转型”,用于决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。
reinterpret_cast
意图执行低级转换,实际动作(及结果)可能取决于编译器,这也就表示它不可移植。例如将一个pointer to int转型为一个int。这一类转型在低级代码以外很少见。
static_cast
用来强迫隐式转换implicit conversions,例如将non-const对象转为const对象,或将int转为double等。它也可用来执行上述多种转换的反向转换,例如将void*指针转为typed指针,将pointer-to-base转为pointer-to-derived。但它无法将const转为non-const——这个只有const_cast才能做。
class Widget{
public:
explicit Widget(int size);
...
};
void doSomeWork(const Widget& w);
doSomeWork(Widget(15)); //以一个int加上“函数风格”的转型动作创建一个Widget
doSomeWork(static_cast(15)); //以一个int加上“C++风格”的转型动作创建一个Widget
int x, y;
...
double d = static_cast(x) / y; //x除以y,使用浮点数除法
建立一个base class指针指向一个derived class对象,但有时候上述两个指针值并不相同。这种情况下会有个偏移量(offset)在运行期被施行于Derived指针上,用以取得正确的Base指针值
class Base { ... };
class Derived: public Base { ... };
Derived d;
Base* pb = &d; //隐喻的将Derived*转换为Base*