C++ Primer阅读笔记[Part 3]

    科技2022-07-14  119

    拷贝赋值与析构

    构造函数(初始化对象的非static数据成员):

    成员初始化在构造函数执行之前完成并且按照他们在类中的顺序进行

    拷贝构造函数:

    第一个参数是自身类引用   任何额外参数都有默认值由于拷贝构造函数可能被隐式引用所以一般不是explicit

    发生的前提条件:

    用=定义变量时从一个返回类型为非引用的函数返回一个对象把一个对象作为实参传递给一个非引用类型的形参用花括号列表初始化一个数组中的元素或一个聚合类中的成员

    析构函数(销毁对象的非static数据成员):

    先执行析构函数体在进行成员销毁,并且销毁顺序按照初始化的逆序, 析构成员时依靠的是成员自己的析构函数如果是内置类型则啥也不需要做。

    如果一个类有了析构函数那就意味着几乎肯定同时会有拷贝构造函数和拷贝赋值运算符,原因是假设没有这两样那编译器会自动帮我们生成合成的拷贝构造函数和合成的拷贝赋值运算符。当我们进行拷贝构造或者赋值的时候,假设有动态内存,就会造成两个对象指向同一块内存空间。当作用域结束,这些变量被销毁时可能导致重复释放同一块内存空间。

    如果一个类有了拷贝构造函数或者拷贝赋值运算符也并不意味着一定需要析构函数。

    拷贝赋值运算符:

    检验一个好的拷贝赋值运算符的标准是:

    1. 能够自己给自己赋值

    2. 组合了析构函数与拷贝构造函数的工作

    由于拷贝赋值运算符需要先把被赋值一方管理的资源先释放掉,然后把通过引用传进来的对象内容赋值给被赋值一方,如果是自己赋值给自己,那先把自己管理的资源delete掉后,在把指向自己的引用赋值给自己就会导致使用已经delete掉的内存

    class HasPtr { public: HasPtr(const string &s = string()) : ps(make_shared<string>(s)), i(0) { } HasPtr(const HasPtr &hasPtr) { ps = make_shared<string>(*(hasPtr.ps)); i = hasPtr.i; } HasPtr &operator=(const HasPtr &hasPtr) { // 先进行拷贝 string strNew = *(hasPtr.ps); // 传入拷贝的内容 ps = make_shared<string>(strNew); i = hasPtr.i; return(*this); } HasPtr& HasPtr::operator=(HasPtr&& rhs) noexcept { cout << "MoveAssignment" << endl; if (this != &rhs) { delete ps; ps = rhs.ps; rhs.ps = nullptr; rhs.i = 0; } return(*this); } ~HasPtr() { } private: shared_ptr<string> ps; int i; };

    正确的做法是: 先复制一份需要赋值的内容,然后在删除被赋值一方的资源,接着在把那份拷贝赋值给被赋值的一方。

    文本查询程序(C++ Primer中12.3习题):

    // 这是我个人的版本 class QueryText { public: QueryText() = delete; QueryText(const string &strFileName, const string &strNeedToFind); bool ReadContent(); void PrintResults(); void GetResults(); ~QueryText(); private: int MatchingWordAlgorithm(const string &cstrString, const string &cstrMatch); int MatchingWordAlgorithmWithSTL(const string &cstrString, const string &cstrMatch); string __itos(int iNum); private: vector<string> m_vecReadContent; long m_lTotalLines; long m_lCorrespondingNum; // 行号 单词 出现次数 map<int, pair<string, int> > m_mapResults; string m_strFinding; string m_strFileName; }; QueryText::QueryText(const string &strFileName, const string &strNeedToFind) : m_strFileName(strFileName), m_strFinding(strNeedToFind), m_lTotalLines(0), m_lCorrespondingNum(0) { if (ReadContent()) { GetResults(); PrintResults(); } } QueryText::~QueryText() { if (!m_mapResults.empty()) m_mapResults.clear(); if (!m_strFileName.empty()) m_strFileName.clear(); } void QueryText::GetResults() { int iCnt = 0, iLineNum = 0; pair<string, int> pir; for (const string &s : m_vecReadContent) { iCnt = MatchingWordAlgorithmWithSTL(s, m_strFinding); if (iCnt > 0) m_lTotalLines++; iLineNum++; pir = make_pair(s, iCnt); m_mapResults[iLineNum] = pir; } } int QueryText::MatchingWordAlgorithmWithSTL(const string &cstrString, const string &cstrMatch) { string::size_type finding = 0; int iCnt = 0; if (cstrString.empty() || cstrString.size() < cstrMatch.size()) return(-1); while ((finding = cstrString.find(cstrMatch, finding)) != string::npos) { ++iCnt; ++finding; } return(iCnt); } int QueryText::MatchingWordAlgorithm(const string &cstrString, const string &cstrMatch) { unsigned uiCnt = 0; bool fLastTime = false; for (unsigned i = 0, j = 0; i < cstrString.size(); ++i, ++j) { if (cstrString[i] != cstrMatch[j]) { if (fLastTime) { fLastTime = false; --i; } j = -1; } else { if (cstrString[i] == cstrMatch[j]) fLastTime = true; if (j == cstrMatch.size() - 1) { uiCnt++; j = -1; fLastTime = false; } } } return(uiCnt); } bool QueryText::ReadContent() { ifstream in(m_strFileName); string strLine; if (!in.is_open()) { cout << "文件读取失败!" << endl; return(false); } while (getline(in, strLine)) m_vecReadContent.push_back(strLine); in.close(); return(true); } string QueryText::__itos(int iNum) { string s; if (!iNum) return("0"); while (iNum) { s += ('0' + iNum % 10); iNum /= 10; } reverse(s.begin(), s.end()); return(s); } void QueryText::PrintResults() { if (m_mapResults.empty()) { cout << "没有任何结果, 请重新读入内容" << endl; return; } auto mmiter = m_mapResults.cbegin(); while (mmiter != m_mapResults.cend()) { auto pir = mmiter->second; cout << "(line " << mmiter->first << ") " << pir.first << " occur times: " << ((pir.second == -1) ? "空行" : __itos(pir.second)) << endl; mmiter++; } cout << "拥有该单词的总行数: " << m_lTotalLines << endl; return; } int main() { QueryText("C:\\Users\\ki0pl\\Desktop\\a.txt", "on"); system("pause"); return(0); }

    另一个例子:

    // 书上的版本 // .h #pragma once #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <vector> #include <array> #include <deque> #include <list> #include <forward_list> #include <memory> #include <fstream> #include <sstream> #include <set> #include <map> #include <unordered_map> #include <unordered_set> #include <utility> #include <ctime> #include <algorithm> #include <functional> #include <numeric> #include <windows.h> #include <stack> #include <queue> #include <stdexcept> #include <iterator> #include <allocators> using namespace std; using namespace placeholders; class QueryResult; class TextQuery { public: using line_no = vector<string>::size_type; TextQuery(ifstream &); QueryResult query(const string &) const; private: shared_ptr<vector<string> > file; map<string, shared_ptr<set<line_no> > > wm; }; class QueryResult { friend ostream &print(ostream &, const QueryResult &); using line_no = vector<string>::size_type; public: QueryResult(string s, shared_ptr<set<line_no> > p, shared_ptr<vector<string> > f) : sought(s), lines(p), file(f) { } private: string sought; shared_ptr<set<line_no> > lines; shared_ptr<vector<string> > file; }; // .cpp #include "TextQuery.h" TextQuery::TextQuery(ifstream &is) : file(new vector<string>) { string text; while (getline(is, text)) { file->push_back(text); int n = file->size() - 1; istringstream line(text); string word; while (line >> word) { auto &lines = wm[word]; if (!lines) lines.reset(new set<line_no>); lines->insert(n); } } } QueryResult TextQuery::query(const string &sought) const { static shared_ptr<set<line_no> > nodate(new set<line_no>); auto loc = wm.find(sought); if (loc == wm.end()) return(QueryResult(sought, nodate, file)); else return(QueryResult(sought, loc->second, file)); } string make_plural(size_t n, string wrd, string addStuffs) { if (n > 1) return(wrd + addStuffs); else return(wrd); } ostream &print(ostream &os, const QueryResult &qr) { os << qr.sought << " occurs " << qr.lines->size() << " " << make_plural(qr.lines->size(), "time", "s") << endl; for (auto num : *qr.lines) os << "\t(line " << num + 1 << ") " << *(qr.file->begin() + num) << endl; return(os); } // main #include "TextQuery.h" int main() { ifstream in("C:\\Users\\ki0pl\\Desktop\\a.txt"); TextQuery t(in); QueryResult q = t.query("on"); print(cout, q); in.close(); system("pause"); return(0); }

    对象移动(C++11新特性)

    有一些对象不支持拷贝,但支持移动。比如unique_ptr智能指针,IO类等。其他的比如标准库容器,string和shared_ptr即支持移动也支持拷贝

     

     

    重载运算与类型转换

    重载运算符的类型:

    一元运算符 重载++, -- 等只需一个操作数的操作符二元运算符: 重载需要两个操作数才能执行的操作符

    注意:

    除了函数调用运算符外,其他重载的运算符不能有默认实参如果重载的运算符是类成员函数则this指针被绑定到第一个运算对象上,即类内运算符重载函数比类外少一个参数如果重载运算符函数并非类的成员函数则以友元函数存在于类内对于运算符重载函数必须至少含有一个类类型参数,所以无法重载内置类型我们重载已存在的运算符无法发明新的运算符作用域运算符(::), 三目运算符(: ?), .*运算符以及点号运算符(.)四个运算符无法被重载逻辑与运算符,逻辑或运算符,逗号运算符的对象求值顺序规则以及&&和||的短路规则无法被保留,所以不应该被重载

    等价关系:

    class a, b; // 两者等价 operator+(a, b); a + b; // 两者等价 a.operator+=(b); a += b;

    1. 箭头运算符(->)

    箭头运算符必须是类成员,解引用通常也是类成员,但并非必须->重载永远是获取类成员, 比如point->mem, point必须是类对象指针或者重载了->的类对象重载的箭头运算符必须返回指针或者定义了箭头运算符的某个类的对象 class Origin { public: Origin() : s(new string("Hello, world")) { } string operator->() { cout << *s << endl; system("pause"); if (s->empty()) return(""); else return(*s); } private: string *s; }; class Now { public: Now(shared_ptr<Origin> p) : m_p(p) { } string operator->() { return(m_p->operator->()); } private: shared_ptr<Origin> m_p; }; int main() { shared_ptr<Origin> o(new Origin); Now n(o); n.operator->(); return(0); }

    2. 函数调用运算符( () )

    函数调用运算符必须是类成员, 但函数指针运算符可有多个重载

    class PrintInt { public: PrintInt(ostream& o = cout, char c = ' ') : os(o), sep(c) { } void operator()(const int &s) const { os << s << sep; } private: ostream& os; char sep; }; class FuncObj { public: void operator()(int i); size_t size() const { return(vec.size()); } vector<int>::const_iterator begin() const { return(vec.begin()); } vector<int>::const_iterator end() const { return(vec.end()); } private: vector<int> vec; }; void FuncObj::operator()(int i) { vec.push_back(i); } class GetString { public: GetString(istream &is = cin) : m_is(is) { } string operator()() { getline(m_is, m_str); if (m_str.empty()) return(""); else return(m_str); } private: string m_str; istream& m_is; }; void replace_item(char newC, char oldC, char ifInOld, shared_ptr<string> p) { static int i = 0; if (oldC == ifInOld) (*p)[i] = newC; ++i; } class Replace { public: Replace(string &s, char ch) : m_ch(ch), m_p(make_shared<string>(s)) { } void operator()(char newC) { // for_each(m_p->begin(), m_p->end(), bind(replace_item, newC, _1, m_ch, m_p)); // lambda版本 for_each(m_p->begin(), m_p->end(), \ [this, newC](char c)->void {static int n = 0; if (m_ch == c) (*m_p)[n] = newC; ++n; }); cout << *m_p << endl; } private: char m_ch; shared_ptr<string> m_p; }; int main() { FuncObj obj; int i; while (obj.size() < 5) { cin >> i; cin.get(); // 去除回车,防止影响下面的GetString() obj(i); } for_each(obj.begin(), obj.end(), PrintInt()); cout << endl; cout << "请输入一串字符串: "; cout << GetString()() << endl; string s1 = "Hello, world! fuck you bitch"; Replace rep(s1, 'o'); rep('k'); return(0); }

    关于函数对象

    如果类重载了函数调用运算符并实例化出的对象被成为函数对象lambda也是函数对象, 其会生成一个未命名类的未命名对象,其中包含函数调用运算符的重载默认情况下lambda生产的函数对象是const的,除非加了mutable关键字lambda表达式产生的类不含有默认构造函数, 赋值运算符以及默认析构函数,关于默认拷贝/移动构造函数是否存在视类成员情况而定 class CheckLen { public: CheckLen(const string& s, size_t maximum = 0) : m_s(s), m_Max(maximum) { set<int> se = checkLenOfEchWrd(); for (set<int>::const_iterator is = se.cbegin(); is != se.cend(); ++is) { // 利用单词长度来创建单词长度与单词vector的映射 shared_ptr<vector<string> > sv(new vector<string>); m[*is] = *sv; } } void operator()() { statistics(); } bool isEqualToThreshold() { if (m_s.size() == m_Max) return(true); return(false); } void statistics() { vector<string> vec; vec = splitSentence(); for (auto& s : vec) { if (s.size() <= 10) m[s.size()].push_back(s); } for (map<int, vector<string> >::const_iterator it = m.cbegin(); it != m.cend(); ++it) { if (it->second.size() > 0) { cout << "字母数量为" << it->first << "的单词有" << it->second.size() << "个, 分别是: " << endl; for (auto& k : it->second) cout << k << " "; cout << endl; } } } private: set<int> checkLenOfEchWrd() { // 找到vector<string>数组内单词元素长度的集合 vector<string> v = splitSentence(); set<int> se; for (vector<string>::size_type s = 0; s != v.size(); ++s) se.insert(v[s].size()); return(se); } // 解决标点符号导致spilit时把标点一起算进去的问题 void RidOfPunct(string& s) { for (string::size_type i = 0; i < s.size(); ++i) { if (ispunct(s[i])) { for (int j = i; j < s.size(); ++j) s[j] = s[j + 1]; s[s.size() - 1] = 0; } } } vector<string> splitSentence() { vector<string> vec; string sTmp; size_t EndPos = 0; // 每一段空格开始的index, 减去1就是每一段的结束index size_t NoSpaceOfst = 0; // 每一段的开始index string sSave = m_s; RidOfPunct(sSave); while ((NoSpaceOfst = sSave.find_first_not_of(' ', NoSpaceOfst)) != string::npos) { // 循环判断,寻找word的第一个下标和空格的第一个下标 if ((EndPos = sSave.find(' ', NoSpaceOfst)) != string::npos) { sTmp = sSave.substr(NoSpaceOfst, EndPos - NoSpaceOfst); vec.push_back(sTmp); NoSpaceOfst = EndPos; } if (NoSpaceOfst != string::npos && EndPos == string::npos) { // 排除"xxxx "导致少算一个word的情况 vec.push_back(sSave.substr(NoSpaceOfst)); break; } } return(vec); } private: size_t m_Max; const string& m_s; map<int, vector<string> > m; }; int main() { string s = "I lo!ve t!o ea,t shi?t, I lov,e bitch!"; CheckLen ck(s); ck(); system("pause"); return(0); } class IsShorter { public: bool operator()(const string &s1, const string &s2) { return(s1.size() < s2.size()); } }; class NotShorterThan { public: NotShorterThan(int len) : minLen(len) { } bool operator()(const string &s) { return(s.size() >= minLen); } private: int minLen; }; class PrintString { public: void operator()(const string &str) { cout << str << " "; } }; void elimDups(vector<string> &wds) { unique(wds.begin(), wds.end()); } void biggies(vector<string>& words, vector<string>::size_type sz) { elimDups(words); stable_sort(words.begin(), words.end(), IsShorter()); // 有小到大排序 auto wc = find_if(words.begin(), words.end(), NotShorterThan(sz)); auto count = words.end() - wc; auto make_plural = [](int n, const string& s, char ch)->string {if (n > 1) return(s + ch); else return(s); }; cout << count << " " << make_plural(count, "word", 's') << " of length " << sz << " or longer" << endl; PrintString ps; for_each(wc, words.end(), ps); cout << endl; }

    注意:

    赋值(=), 下标([]), 调用( () )和成员访问运算符(->)必须是成员。复合赋值运算符一般而言是成员但并非必须会改变对象状态的运算符比如递增, 递减和解引用通常应该是成员具有对称性的运算符可能转换成任意一端的运算对象,比如运算,相等性,关系和位运算等,因此他们通常应该是普通的非成员函数。调用的时候至少要有一个是类类型

    标准库定义的函数对象

    sort(svec.begin(), svec.end(), greater<string>) vector<string *> nameTbl; sort(nameTbl.begin(), nameTbl.end(), less<string>()); count_if(vec.begin(), vec.end(), bind(greater<int>(), 1024)); find_if(vec.begin(), vec.end(), bind(not_equal_to<string>(), "pooh")); transform(vec.begin(), vec.end(), vec.begin(), bind(multiplies<int>(), 2));

    标准库function类型(C++11新特性 functional.h)

    注意:

    function是一个模板, 比如以function<int(int, int)> 形式声明或定义个人感觉像是一个函数指针的升级版 #include <functional> int add(int a, int b) { return(a + b); } class divide { public: int operator()(int a, int b) { return(a / b); } }; string returnStr(string a) { return(a); } int main() { function<string(string)> f = returnStr; cout << f("Hello") << endl; map<string, int(*)(int, int)> binopsx; // 不可以放入lambda表达式或者标准库函数对象 auto mod = [](int a, int b) {return(a % b); }; map<string, function<int(int, int)> > binops = { {"+", add}, {"-", minus<int>()}, {"/", divide()}, {"*", [](int a, int b) {return(a * b); }}, {"%", mod} }; cout << binops["+"](10, 5) << endl; cout << binops["-"](10, 5) << endl; cout << binops["*"](10, 5) << endl; cout << binops["/"](10, 5) << endl; cout << binops["%"](10, 5) << endl; system("pause"); return(0); }

    关于function二义性问题

    int add(int a, int b) { return(a + b); } int add(string a, string b) { return(stoi(a) + stoi(b)); } int main() { function<int(int, int)> f = add; // 报错, function无法分辨是哪一个,辨别与形参无关.即function无法分清重载函数 system("pause"); return(0); }

    解决方法:

    函数指针 int (*pf)(int, int) = add; function<int(int, int)> f = pf; // 与另一个add区分开来 lambda表达式 function<int(int, int)> f = [](int a, int b){return(a + b)}; // 直接使用lambda

    4. 输出运算符(<<)

    注意:

    输出运算符应该负责打印对象内容而非控制格式,比如输出运算符的重载不应该输出换行运算符必须为非成员友元函数,因为其第一个运算对象必须为ostream的引用,如果为类成员函数则第一个对象变成该对象。如果必须是类内成员函数则必须是ostream类的成员,而其为标准库所以是不可能的 class Date { friend ostream& operator<<(ostream &os, const Date &d); public: Date() : yr(0), mon(0), day(0) { } private: int yr; int mon; int day; }; ostream& operator<<(ostream& os, const Date& d) { os << d.yr << "年" << d.mon << "月" << "日" << endl; return(os); }

    5. 输入运算符(>>)

    注意:

    如果输入类型不符合的对象会导致istream类型变量为NULL,所以输入可以先检查istream &变量遇到文件末尾也会出现错误即NULL // 接上个例子 ostream& operator<<(ostream& os, const Date& d) { os << d.yr << "年" << d.mon << "月" << "日" << endl; return(os); }

    6. 算数与关系运算符

    注意:

    一般情况下都会被定义成非成员函数以允许对左侧或右侧对象进行转换形参一般都是const常量引用一般定义了算数运算符也会定义一个与之对应的复合赋值运算符,比如定义了+则在定义一个+=, 这样定义加号时就可以直接用+=来进行赋值了 class String { friend ostream& operator<<(ostream& os, const String& s); friend istream& operator>>(istream& is, String& s); friend String operator+(const String &lhs, const String &rhs); public: String(); String(const char* s); String(const String& s); String& operator=(const String& s); String &operator+=(const String &s); String& operator=(initializer_list<string> il); char &operator[](size_t n); const char &operator[](size_t n) const; ~String(); private: allocator<char> alloc; char* m_p; char* m_p_start; int m_iLen; }; String& String::operator=(initializer_list<string> il) { string s = *(il.begin()); if (this->m_iLen != -1) { this->~String(); } m_p_start = alloc.allocate(s.size() + 1); m_iLen = s.size(); int i = 0; auto start = m_p_start; while (i < m_iLen) alloc.construct(m_p_start++, s[i++]); alloc.construct(m_p_start, 0); m_p = m_p_start; m_p_start = start; return(*this); } String::String(const String& s) { int i = 0; m_p_start = alloc.allocate(s.m_iLen + 1); auto start = m_p_start; m_iLen = s.m_iLen; auto p_dst = s.m_p_start; while (i < m_iLen) alloc.construct(m_p_start++, p_dst[i++]); alloc.construct(m_p_start, 0); m_p = m_p_start; m_p_start = start; } String& String::operator=(const String& s) { if (this->m_iLen != -1) { this->~String(); m_iLen = s.m_iLen; m_p_start = alloc.allocate(m_iLen + 1); int i = 0; char* p = s.m_p_start; auto s = m_p_start; while (i < m_iLen) alloc.construct(m_p_start++, p[i++]); alloc.construct(m_p_start, 0); m_p = m_p_start; m_p_start = s; } else { m_iLen = 0; m_p_start = m_p = NULL; } return(*this); } ostream& operator<<(ostream& os, const String& s) { auto pTmp = s.m_p_start; os << pTmp; return(os); } istream& operator>>(istream& is, String& s) { string strContent; int i = 0; if (s.m_iLen <= 0) { s.~String(); // 如果s不为-1或者0代表这个s内部有内容先进行析构 } cout << "请输入字符串: "; getline(is, strContent); s.m_iLen = strContent.size(); s.m_p_start = s.alloc.allocate(s.m_iLen + 1); auto pTmp = s.m_p_start; while (i < s.m_iLen) s.alloc.construct(s.m_p_start++, strContent[i++]); *(s.m_p_start) = 0; s.m_p = s.m_p_start; s.m_p_start = pTmp; return(is); } String operator+(const String& lhs, const String& rhs) { if (lhs.m_iLen == -1 && rhs.m_iLen == -1 || (lhs.m_iLen == 0 && rhs.m_iLen == 0)) // 有可能全部都为被析构对象 return(String()); if (lhs.m_iLen == -1 || (lhs.m_iLen == 0)) // 只有一个 return(String(rhs)); if (rhs.m_iLen == -1 || (rhs.m_iLen == 0)) return(String(lhs)); String s(lhs); s += rhs; return(s); } String::String() { m_p_start = alloc.allocate(1); alloc.construct(m_p_start, '\0'); m_p = m_p_start; m_iLen = 0; } String::String(const char* s) { m_iLen = strlen(s); m_p = alloc.allocate(m_iLen + 1); m_p_start = m_p; int i = 0; while (i < m_iLen) { alloc.construct(m_p++, s[i++]); } alloc.construct(m_p, 0); } String &String::operator+=(const String& s) // 绕了个大圈子... 凑合着用吧 { String sTmp; int i = 0; if (s.m_iLen == 0 || s.m_iLen == -1) return(*this); sTmp.m_iLen = this->m_iLen + s.m_iLen; string Total(this->m_p_start); Total += s.m_p_start; auto pStart = sTmp.alloc.allocate(sTmp.m_iLen + 1); sTmp.m_p_start = pStart; while (i < sTmp.m_iLen) sTmp.alloc.construct(pStart++, Total[i++]); *pStart = 0; sTmp.m_p = pStart; *this = sTmp; return(*this); } String::~String() { auto p = m_p; while (m_p_start != p) { alloc.destroy(p--); } alloc.deallocate(m_p_start, m_iLen + 1); m_p_start = NULL; m_p = NULL; m_iLen = -1; } char &String::operator[](size_t n) { return(m_p_start[n]); } const char &String::operator[](size_t n) const { return(m_p_start[n]); }

    7. 赋值运算符

    注意:

    无论形参类型是什么,赋值运算符重载都必须是成员函数对于复合赋值运算符来说不一定必须是成员函数,但最好是定义在类的内部并且返回值返回左侧对象的引用 // 接上部 String& String::operator=(initializer_list<string> il) { string s = *(il.begin()); if (this->m_iLen != -1) { this->~String(); } m_p_start = alloc.allocate(s.size() + 1); m_iLen = s.size(); int i = 0; auto start = m_p_start; while (i < m_iLen) alloc.construct(m_p_start++, s[i++]); alloc.construct(m_p_start, 0); m_p = m_p_start; m_p_start = start; return(*this); }

    8. 下标运算符([])

    注意:

    下标运算符必须是成员函数!通常会定义两个版本, 一个是普通引用,另一个是常量引用如果对象是const的,那对象对象使用下标运算符时调用的就是常量引用版本的下标运算符重载反之也是 // 接上 char &String::operator[](size_t n) { return(m_p_start[n]); } const char &String::operator[](size_t n) const { return(m_p_start[n]); }

    9. 递增递减运算符

    注意:

    不强制要求定义成类成员,但由于他们会改变对象状态,所以建议定义成类成员前置运算符由于直接返回自身所以要返回引用,而后置返回的是对象的拷贝所以直接返回值就可以为区分前置后置元素符号,在后置运算符形参中添加一个不会被使用的int类型由于我们不需要使用该int所以无需命名如果要显式调用后置运算符,可以p.operator++(0)来进行 class Int { friend ostream& operator<<(ostream &os, const Int &i); public: Int() : m_iCnt(0) { } Int(int i) : m_iCnt(i) { } Int& operator++() { // 前置 this->m_iCnt += 1; return(*this); } Int operator++(int) { // 后置 Int i = *this; ++*this; return(i); } Int& operator--() { // 前置 this->m_iCnt -= 1; return(*this); } Int operator--(int) { // 后置 Int i = *this; --*this; return(i); } private: int m_iCnt; }; ostream& operator<<(ostream& os, const Int& i) { os << i.m_iCnt; return(os); }

    10. 类型转换运算符

    注意:

    没有形参也没有返回值,必须为类的成员函数形式: operator type() consttype可以是除了void的以外的任意类型只要能作为函数返回值(数组和函数不行,但是其指针可以)类型转换时,如果不存在明确的一一对应的关系就不许要类型转换运算符是隐式执行所以无法给转换运算符函数提供形参也就不用定义虽然类型转换运算符不负责指定返回类型,但实际上每个类的转换函数都会返回一个对应类型的值 class SmallInt { public: SmallInt(int i = 0) : val(i) { if (i < 0 || i > 255) throw out_of_range("Bad smallInt value"); // 看起来像是一个unsigned char类型 } operator int() const { return(val); } private: size_t val; }; int main() { SmallInt s; s = 3; // 把int隐式转换成SmallInt类型 s = s + 4; // operator int()把s转换成int后与4相加并通过拷贝赋值运算符赋值给自己 SmallInt si = 3.14; // 同上,只是int把0.14的精度去掉了 si = si + 3.14; system("pause"); return(0); }

    为了防止隐式转换有异常,所以可以直接使用显示的类型转换运算符

    explicit operator int() const { return(val); } int main() { SmallInt si = 3; si + 3; // 这里错误,因为类型转换运算符是显式的,所以必须显式转换后使用 static_cast<int>(si) + 3; // 正确 system("pause"); return(0); }

    注意:

    如果表达式被用于作为条件则显式表达式将隐式执行,比如if, while, do,for的条件部分,!, ||, &&运算符对象以及? : 三目运算符向bool类型转换通常用在条件部分,因此operator bool一般定义成explicit的为了防止二义性,一般不要为类定义相同的类型转换, 比如A定义了转换成B,B定义了转换成A。也不要在一个类中定义了多个算数类型的转换规则假设有一套转换成自己定义类的类型转换运算符与另一套转换成标准类型的转换。使用转换标准类型的那个如果定义了向算数类型转换的运算符并且重载了算数类型运算符可能导致二义性。因为既可以使用算数类型运算符进行运算,也可以把对象转成相对于类型进行运算 struct B; struct A { A() = default; A(const B &); // 即构造函数可以隐式方式把B转成A }; struct B { operator A() const; // 通过类型转换运算符把B转成A } A f(const A&); B b; A a = f(b); // 错误, 这里可以是A::A(const B &) 也可以是A::operator A() const A a1 = f(b.operator A()); // 显式调用,正确 A a2 = f(A(b)); // 显式调用, 正确 // 多个算数类型转换的二义性 struct A { A(int = 0); A(double); operator int() const; operator double() const; // 其他成员 }; void f2(long double); A a; f2(a) // 可能是A::operator int()转换成int也可能是A::operator double()转换成double long lg; A a2(lg); void calc(int); void calc(A); double b; calc(b) // 这里使用void calc(int)因为int是标准类型而A是我们自己定义的,虽然A中有面向double的类型转换运算符

    总结:  除了显式向bool类型转换外,应该尽量避免定义类型转换函数并尽可能限制那些显然正确的非显式构造函数

    上面那句话是书上的原话,我的理解是,尽量少用类型转换运算符,就算用也只用需要向bool类型转换,并且该转换要是explicit的。为了防止构造函数与类型转换产生二义性,构造函数尽量定义成explicit的。

    (完)

     

    Processed: 0.018, SQL: 8