#
1、const其實(shí)并不是真正的常量(P32)

2、早期的gets()中的Bug導(dǎo)致了Internet蠕蟲(chóng)(P42)
gets()函數(shù)并不檢查緩沖區(qū)的空間,事實(shí)上它也無(wú)法檢查緩沖區(qū)的空間。如果函數(shù)的調(diào)用者提供了一個(gè)指向堆棧的指針,并且gets()函數(shù)讀入的字符數(shù)量超過(guò)緩沖區(qū)的空間,gets()函數(shù)將會(huì)愉快地將多出來(lái)的字符繼續(xù)寫入到堆棧中,這就覆蓋了堆棧原先的內(nèi)容。——這就是病毒利用它來(lái)寫入額外空間,并引發(fā)蠕蟲(chóng)病毒的前提。
推薦的方式是將
gets(line)
替換為
if(fgets(line, sizeof(line), stdin) == NULL)
exit(1);
3、相鄰字符串常量自動(dòng)連接(P45)
這個(gè)其實(shí)已經(jīng)應(yīng)用很普遍了,但是我個(gè)人用的比較少,特此記錄一下。
4、返回一個(gè)指針?(P48)
這個(gè)話題圍繞一個(gè)程序的BUG來(lái)展開(kāi),這個(gè)程序返回了局部變量的值的指針,這么說(shuō)當(dāng)然你一眼就能看得出來(lái)問(wèn)題所在,但是在很多時(shí)候,這個(gè)錯(cuò)誤卻總是在你的眼皮子底下溜走。
作者提供了五種方式,只能說(shuō)可以用,但唯一推薦的只有一個(gè),詳見(jiàn)作者的分析(P48)(不是什么高深的理論,你自己也能分析地出來(lái))。
a.返回一個(gè)字符串常量的指針。因?yàn)槌A看嬖陟o態(tài)數(shù)據(jù)存儲(chǔ)區(qū),所以指針沒(méi)問(wèn)題。
b.使用全局聲明的數(shù)組。提到全局兩個(gè)字,就知道這個(gè)方法有很大的局限性。
c.使用靜態(tài)數(shù)組。下一次調(diào)用將覆蓋這個(gè)數(shù)組內(nèi)容。
char * func() {
static char buffer[20];
…
return buffer;
}
d.顯式分配一些內(nèi)存,保存返回的值。
char * func() {
char * s = malloc(120);
…
return s;
}
既然用到了malloc,就必然伴隨著free,因此帶來(lái)了內(nèi)存管理的問(wèn)題,增加了開(kāi)發(fā)者負(fù)擔(dān)。
e.(推薦)在調(diào)用前后,由函數(shù)調(diào)用者分配內(nèi)存,并由其釋放,在同一地方釋放對(duì)于內(nèi)存管理來(lái)說(shuō)代價(jià)相對(duì)最小。
void func( char * result, int size) {
…
strncpy(result, “That’d be in the data segment, Bob”, size);
}
buffer = malloc(size);
func(buffer, size);
…
free(buffer);
快速排序法:(好土,感覺(jué)滿世界都會(huì),不過(guò)還是寫一下,當(dāng)然了,標(biāo)準(zhǔn)庫(kù)里多的是排序算法),這里還是實(shí)現(xiàn)經(jīng)典版的快速排序了,時(shí)間復(fù)雜度O(nlogn)
Algorithms.h
#pragma once
#include <iostream>
class Algorithms
{
public:
Algorithms(void);
~Algorithms(void);
public:
template <typename T>
static void QuickSort(T* arr, size_t min, size_t max);
private:
template <typename T>
static size_t qsort_helper_partition(T* arr, size_t min, size_t max);
template <typename T>
static inline void swap(T* arr, size_t x, size_t y);
};
template <typename T>
void Algorithms::QuickSort(T* arr, size_t min, size_t max)
{
if(min >= max || max == 0 - 1) return;
size_t p = qsort_helper_partition(arr, min, max);
QuickSort(arr, min, p - 1);
QuickSort(arr, p + 1, max);
}
template <typename T>
size_t Algorithms::qsort_helper_partition(T* arr, size_t min, size_t max)
{
T cmp = arr[min];
int i = min + 1, j = max;
while(true)
{
while(cmp < arr[i])
++i;
while(arr[j] < cmp)
--j;
if(i >= j) break;
swap(arr, i, j);
}
swap(arr, min, j);
return j;
}
template <typename T>
void Algorithms::swap(T* arr, size_t x, size_t y)
{
T tmp = arr[x];
arr[x] = arr[y];
arr[y] = tmp;
}
用法:(順便有標(biāo)準(zhǔn)庫(kù)的排序法,當(dāng)然只是調(diào)一下,沒(méi)有什么可說(shuō)的了)
#include "Algorithms.h"
#include <iostream>
#include <vector>
#include <algorithm>
int _tmain(int argc, _TCHAR* argv[])
{
int arr[] = {4, 8, 3, 7, 1, 5, 6, 2};
for(size_t i = 0; i != 8; ++i)
{
std::cout<<arr[i]<<" ";
}
std::cout<<std::endl;
Algorithms::QuickSort(arr,0, 7);
for(size_t i = 0; i != 8; ++i)
{
std::cout<<arr[i]<<" ";
}
std::cout<<std::endl;
std::vector<int> vec;
vec.push_back(3);
vec.push_back(1);
vec.push_back(4);
vec.push_back(1);
vec.push_back(7);
vec.push_back(6);
for(std::vector<int>::iterator iter = vec.begin();
iter != vec.end(); ++ iter)
{
std::cout<<*iter<<" ";
}
std::cout<<std::endl;
std::sort(vec.begin(), vec.end());
for(std::vector<int>::iterator iter = vec.begin();
iter != vec.end(); ++ iter)
{
std::cout<<*iter<<" ";
}
std::cout<<std::endl;
return 0;
}


以上內(nèi)容摘自《編程之美》P150-154。
為了方便使用,下面是可拷貝的代碼:
Math.h
#pragma once
class Math
{
public:
Math(void);
~Math(void);
public :
//編程之美P150-154
//求最大公約數(shù),歐幾里德——輾轉(zhuǎn)相除法
static int Gcd1(int x, int y);
//求最大公約數(shù),歐幾里德——輾轉(zhuǎn)相除法(變相將除法變成了減法)
static int Gcd2(int x, int y);
static int Gcd3(int x, int y);
inline static bool IsEven(int x);
inline static int Absolute(int x);
};
Math.cpp
#include "Math.h"
Math::Math(void)
{
}
Math::~Math(void)
{
}
int Math::Gcd1(int x, int y)
{
//y, x%y順序不能錯(cuò);
return y ? Gcd1(y, x % y) : x;
}
int Math::Gcd2(int x, int y)
{
//與Gcd1相同的方式,但由于x%y計(jì)算速度較x-y要慢,但效果相同,所以換用x - y
// 但用減法和除法不同的是,比如和,%20=10,-20=70,也就是-4×=10
// 也就是說(shuō)迭代次數(shù)較Gcd1而言通常是增加了。
return y ? Gcd1(y, x - y) : x;
}
int Math::Gcd3(int x, int y)
{
if(x < y)
return Gcd3(y, x);
if(y == 0)
return x;
else
{
if(IsEven(x))
{
if(IsEven(y))
return (Gcd3(x >> 1, y >> 1) << 1);
else
return Gcd3(x >> 1, y);
}
else
{
if(IsEven(y))
return Gcd3(x, y >> 1);
else
return Gcd3(y, x - y);
}
}
}
bool Math::IsEven(int x)
{
return !(bool)x & 0x0001;
}
int Math::Absolute(int x)
{
return x < 0 ? -x : x;
}
Main.cpp
#include <stdafx.h>
#include <iostream>
#include "Math.h"
using namespace std;
int _tmain(const int & arg)
{
cout<<"Math::Gcd1(42,30) = "<<Math::Gcd1(42,30)<<endl;
cout<<"Math::Gcd1(30,42) = "<<Math::Gcd1(30,42)<<endl;
cout<<"Math::Gcd1(50,50) = "<<Math::Gcd1(50,50)<<endl;
cout<<"Math::Gcd1(0,0) = "<<Math::Gcd1(0,0)<<endl;
cout<<"Math::Gcd1(-42,-30) = "<<Math::Gcd1(-42,-30)<<endl;
cout<<"Math::Gcd1(-42,30) = "<<Math::Gcd1(-42,30)<<endl;
cout<<"------------------------------"<<endl;
cout<<"Math::Gcd2(42,30) = "<<Math::Gcd2(42,30)<<endl;
cout<<"Math::Gcd2(30,42) = "<<Math::Gcd2(30,42)<<endl;
cout<<"Math::Gcd2(50,50) = "<<Math::Gcd2(50,50)<<endl;
cout<<"Math::Gcd2(0,0) = "<<Math::Gcd2(0,0)<<endl;
cout<<"Math::Gcd2(-42,-30) = "<<Math::Gcd2(-42,-30)<<endl;
cout<<"Math::Gcd2(-42,30) = "<<Math::Gcd2(-42,30)<<endl;
cout<<"------------------------------"<<endl;
cout<<"Math::Gcd3(42,30) = "<<Math::Gcd3(42,30)<<endl;
cout<<"Math::Gcd3(30,42) = "<<Math::Gcd3(30,42)<<endl;
cout<<"Math::Gcd3(50,50) = "<<Math::Gcd3(50,50)<<endl;
cout<<"Math::Gcd3(0,0) = "<<Math::Gcd3(0,0)<<endl;
cout<<"Math::Gcd3(-42,-30) = "<<Math::Gcd3(-42,-30)<<endl;
cout<<"Math::Gcd3(-42,30) = "<<Math::Gcd3(-42,30)<<endl;
return 0;
}
不過(guò)有一點(diǎn)值得一提,就是所謂性能最好效率最高的Gcd3不支持負(fù)數(shù),也就是最后兩行測(cè)試代碼無(wú)法通過(guò)。但是限于對(duì)負(fù)數(shù)的最大公約數(shù)并沒(méi)有定義,也就是說(shuō)即便上面的Gcd1和Gcd2好像算出了負(fù)數(shù),但它們的結(jié)果沒(méi)有意義。
#include "stdio.h"
#include "stdlib.h"
void foo(int *a, int *b) {
*a = *a + *b;
*b = *a - *b;
*a = *a - *b;
}
int main(void) {
int a = 1, b = 2, c = 3;
foo(&a, &b);
foo(&b, &c);
foo(&c, &a);
printf("%d, %d, %d", a, b, c);
return EXIT_SUCCESS;
}
foo看似繚亂卻是一個(gè)交換函數(shù):
從最后一次做*a和*b的位置開(kāi)始向上:
也就是*b = *a - *b;擴(kuò)展為*b = (*a + *b) - *b展開(kāi)就是*b = *a;也就是將*a的值賦給了*b。(注意到在此之前*b從未改變過(guò))
*a = *a - *b(這時(shí)候不能用上一行的結(jié)論,因?yàn)?a曾經(jīng)改變過(guò)了)擴(kuò)展為*a = (*a + *b) – *a 也就是*a = *b,這里*b是指原始的*b,而不是上一行的結(jié)論,至此就交換完畢。
可以注意到,這里并沒(méi)有使用臨時(shí)變量。所以這是一個(gè)不需要臨時(shí)變量的交換方法。不過(guò)這種方法只支持支持operator+和operator-的數(shù)值計(jì)算(基本上只能用在整數(shù)上,因?yàn)閷?duì)浮點(diǎn)的操作可能涉及到舍入的問(wèn)題)
而且這個(gè)方法還有一個(gè)缺陷,就是對(duì)數(shù)值邊界的判斷,比入MAX_INT+MAX_INT就溢出了,所以它存在一定的局限性。
如圖所示的代碼出現(xiàn)了如圖所示的錯(cuò)誤,誰(shuí)能解釋一下是為什么呢?
雖然在最后include進(jìn)了cpp文件,而且這種做法也在C++ Primer中也是正確的(難道是標(biāo)準(zhǔn)和現(xiàn)實(shí)的差距?)。將代碼稍微變動(dòng),并將cpp部分的內(nèi)容移到.h文件中的include位置即可正確編譯。

在C++中我們應(yīng)該少用指針,多用引用,原因請(qǐng)大家自行搜索。在傳遞數(shù)組的時(shí)候我們需要格外注意,先讓我們看一個(gè)簡(jiǎn)單的范例。
// PassArray.cpp : 定義控制臺(tái)應(yīng)用程序的入口點(diǎn)。
//
#include "stdafx.h"
#include <iostream>
using namespace std;
template <typename T>
void Func1(T, T);
template <typename T>
void Func2(T&, T&);
void Func3(int (&)[10], int (&)[12]);
int _tmain(int argc, _TCHAR* argv[])
{
int a[10], b[12];
Func1(a, b);
Func2(a, b);
Func3(a, b);
return 0;
}
template <typename T>
void Func1(T, T)
{
cout<<"Func1.invoked!"<<endl;
}
template <typename T>
void Func2(T&, T&)
{
cout<<"Func2.invoked!"<<endl;
}
void Func3(int (&m)[10], int (&n)[12])
{
cout<<"Func3.invoked!"<<endl;
}
首先這個(gè)范例無(wú)法編譯通過(guò):
原因就出在類型推斷上。根據(jù)定義,F(xiàn)unc2的類型必須是T&,也就是說(shuō)傳遞實(shí)參的時(shí)候,兩個(gè)形參必須是相同的,而這一點(diǎn)在模板編程中就會(huì)由編譯器來(lái)負(fù)責(zé)推斷。
Func1:
調(diào)用Func1(a, b)則推斷的類型分別是Func1(int*, int*),調(diào)用函數(shù)將會(huì)自動(dòng)將數(shù)組的首地址指針作為實(shí)參進(jìn)行傳遞,因此類型推斷兩形參相同,編譯通過(guò)!
Func2:
調(diào)用Func2(a, b)因?yàn)槲覀兿M匆玫姆绞竭M(jìn)行實(shí)參傳遞,因此需要遵循這樣的規(guī)律:
(P208)如果形參是數(shù)組的引用,編譯器將不會(huì)將數(shù)組實(shí)參轉(zhuǎn)化為指針,而是傳遞數(shù)組引用的本身。在這種情況下,數(shù)組大小成為形參和實(shí)參類型的一部分。
所以推斷類型分別是Func2(int (&)[10], int (&)[12]),因?yàn)閕nt (&)[10] != int (&)[12],所以與T == T相悖!自然也就編譯不過(guò)了!
Func3:
該函數(shù)是Func2的一個(gè)靜態(tài)表示,通過(guò)上面的解釋應(yīng)該很容易理解這個(gè)代碼了。
這不是一個(gè)面向?qū)ο蟮拇a庫(kù),它的存在僅僅只是為了說(shuō)明幾個(gè)函數(shù)調(diào)用,如果要在您的工程中應(yīng)用相關(guān)內(nèi)容,請(qǐng)自行構(gòu)建(這應(yīng)該不難),或者看看我推薦的文檔。
// ProcessAffinity.cpp : 定義控制臺(tái)應(yīng)用程序的入口點(diǎn)。
//
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <vector>
using namespace std;
void DisplayFrequency(ostream &out, LARGE_INTEGER &freq);
struct TimeSpan
{
LARGE_INTEGER *Frequency;
LARGE_INTEGER StartCounter;
LARGE_INTEGER StopCounter;
BOOL HAS_ERROR;
double CalTimeSpan(){
return (StopCounter.QuadPart - StartCounter.QuadPart)/Frequency->QuadPart;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE CurrentProcessHandle;
DWORD ProcessAffinityMask, SystemAffinityMask, AllowProcessAffinity;
CurrentProcessHandle = GetCurrentProcess();
//GetCurrentProcess返回一個(gè)常量,代表當(dāng)前的進(jìn)程句柄
cout<<CurrentProcessHandle<<endl;
cout<<(HANDLE)-1<<endl;
cout<<(void*)-1<<endl;
cout<<(void*)0xffffffff<<endl;
cout<<"-----------------------------"<<endl;
if(GetProcessAffinityMask(CurrentProcessHandle, &ProcessAffinityMask, &SystemAffinityMask))
{
cout<<ProcessAffinityMask<<endl; //0x0001
cout<<SystemAffinityMask<<endl; //0x0001
AllowProcessAffinity = ProcessAffinityMask & SystemAffinityMask;
cout<<AllowProcessAffinity<<endl;
}
LARGE_INTEGER Freq;
typedef vector<LARGE_INTEGER> FreqVec_type;
FreqVec_type FreqVec;
while(FreqVec.size() != 5)
{
if(QueryPerformanceFrequency(&Freq))
{
DisplayFrequency(cout, Freq);
FreqVec.push_back(Freq);
}
Sleep(1000);
}
for(FreqVec_type::iterator iter = FreqVec.begin(); iter!=FreqVec.end(); ++iter)
{
DisplayFrequency(cout, *iter);
}
cout<<"---------------------"<<endl;
//calculate the timeSpan;
TimeSpan ts;
int sleepTime(3123);
ts.Frequency = &Freq;
if(QueryPerformanceCounter(&ts.StartCounter))
{
ts.HAS_ERROR = false;
Sleep(sleepTime);
}
if(!ts.HAS_ERROR)
{
QueryPerformanceCounter(&ts.StopCounter);
}
cout<<ts.CalTimeSpan()<<endl;
cout<<(ts.StopCounter.QuadPart-ts.StartCounter.QuadPart)/sleepTime<<endl;
return 0;
}
void DisplayFrequency(ostream &out, LARGE_INTEGER &freq)
{
out<<"start display!"<<endl;
out<<freq.HighPart<<endl;
out<<freq.LowPart<<endl;
out<<freq.QuadPart<<endl;
out<<"end display!"<<endl;
}
推薦文檔:《使用增強(qiáng)的計(jì)時(shí)器測(cè)量代碼段》(下載可適合打印,已排版,閱讀該文檔需要使用Microsoft Word 2007或相關(guān)閱讀器(后綴docx),如您需要pdf文檔,可以給我留言,我會(huì)將它發(fā)給你,在CSDN的下載中,您可能需要注冊(cè)成為相關(guān)網(wǎng)站的會(huì)員,或者使用積分,如果您覺(jué)得麻煩,可以直接向我索取!點(diǎn)此獲取!)
關(guān)鍵字:句柄, HANDLE, WINDOWS SDK, windows.h,
從廣義上,能夠從一個(gè)數(shù)值拎起一大堆數(shù)據(jù)的東西都可以叫做句柄。句柄的英文是"Handle",本義就是"柄",只是在計(jì)算機(jī)科學(xué)中,被特別地翻譯成"句柄",其實(shí)還是個(gè)"柄"。從一個(gè)小東西拎起一大堆東西,這難道不像是個(gè)"柄"嗎?
然后,指針其實(shí)也是一種"句柄",只是由于指針同時(shí)擁有更特殊的含義——實(shí)實(shí)在在地對(duì)應(yīng)內(nèi)存里地一個(gè)地址——所以,通常不把指針說(shuō)成是"句柄"。但指針也有著能從一個(gè)32位的值引用到一大堆數(shù)據(jù)的作用,這不是句柄又是什么?
Windows系統(tǒng)中有許多內(nèi)核對(duì)象(這里的對(duì)象不完全等價(jià)于"面向?qū)ο蟪绦蛟O(shè)計(jì)"一詞中的"對(duì)象",雖然實(shí)質(zhì)上還真差不多),比如打開(kāi)的文件,創(chuàng)建的線程,程序的窗口,等等。這些重要的對(duì)象肯定不是4個(gè)字節(jié)或者8個(gè)字節(jié)足以完全描述的,他們擁有大量的屬性。為了保存這樣一個(gè)"對(duì)象"的狀態(tài),往往需要上百甚至上千字節(jié)的內(nèi)存空間,那么怎么在程序間或程序內(nèi)部的子過(guò)程(函數(shù))之間傳遞這些數(shù)據(jù)呢?拖著這成百上千的字節(jié)拷貝來(lái)拷貝去嗎?顯然會(huì)浪費(fèi)效率。那么怎么辦?當(dāng)然傳遞這些對(duì)象的首地址是一個(gè)辦法,但這至少有兩個(gè)缺點(diǎn):
暴露了內(nèi)核對(duì)象本身,使得程序(而不是操作系統(tǒng)內(nèi)核)也可以任意地修改對(duì)象地內(nèi)部狀態(tài)(首地址都知道了,還有什么不能改的?),這顯然是操作系統(tǒng)內(nèi)核所不允許的;
操作系統(tǒng)有定期整理內(nèi)存的責(zé)任,如果一些內(nèi)存整理過(guò)一次后,對(duì)象被搬走了怎么辦?
所以,Windows操作系統(tǒng)就采用進(jìn)一步的間接:在進(jìn)程的地址空間中設(shè)一張表,表里頭專門保存一些編號(hào)和由這個(gè)編號(hào)對(duì)應(yīng)一個(gè)地址,而由那個(gè)地址去引用實(shí)際的對(duì)象,這個(gè)編號(hào)跟那個(gè)地址在數(shù)值上沒(méi)有任何規(guī)律性的聯(lián)系,純粹是個(gè)映射而已。
在Windows系統(tǒng)中,這個(gè)編號(hào)就叫做"句柄"。
?
Handle在Windows中的含義很廣泛,以下關(guān)于談到的Handle除非特別說(shuō)明,將僅限于進(jìn)程、線程的上下文中。
1、先來(lái)談?wù)凥andle
Handle本身是一個(gè)32位的無(wú)符號(hào)整數(shù),它用來(lái)代表一個(gè)內(nèi)核對(duì)象。它并不指向?qū)嶋H的內(nèi)核對(duì)象,用戶模式下的程序永遠(yuǎn)不可能獲得一個(gè)內(nèi)核對(duì)象的實(shí)際地址(一般情況下)。那么Handle的意義何在?它實(shí)際上是作為一個(gè)索引在一個(gè)表中查找對(duì)應(yīng)的內(nèi)核對(duì)象的實(shí)際地址。那么這個(gè)表在哪里呢?每個(gè)進(jìn)程都有這樣的一個(gè)表,叫句柄表。該表的第一項(xiàng)就是進(jìn)程自己的句柄,這也是為什么你調(diào)用GetCurrentProcess()總是返回0x7FFFFFFF原因。
簡(jiǎn)單地說(shuō),Handle就是一種用來(lái)"間接"代表一個(gè)內(nèi)核對(duì)象的整數(shù)值。你可以在程序中使用handle來(lái)代表你想要操作的內(nèi)核對(duì)象。這里的內(nèi)核對(duì)象包括:事件(Event)、線程、進(jìn)程、Mutex等等。我們最常見(jiàn)的就是文件句柄(file handle)。
另外要注意的是,Handle僅在其所屬的進(jìn)程中才有意義。將一個(gè)進(jìn)程擁有的handle傳給另一個(gè)進(jìn)程沒(méi)有任何意義,如果非要這么做,則需要使用DuplicateHandle(),在多個(gè)進(jìn)程間傳遞Handle是另外一個(gè)話題了,與這里要討論的無(wú)關(guān)。
2、進(jìn)程ID
首先,進(jìn)程ID是一個(gè)32位無(wú)符號(hào)整數(shù),每個(gè)進(jìn)程都有這樣的一個(gè)ID,并且該ID在系統(tǒng)范圍內(nèi)是唯一的。系統(tǒng)使用該ID來(lái)唯一確定一個(gè)進(jìn)程。
深入些說(shuō),系統(tǒng)可能使用進(jìn)程ID來(lái)計(jì)算代表該進(jìn)程的內(nèi)核對(duì)象的基地址(及EPROCESS結(jié)構(gòu)的基地址),具體的計(jì)算公式你可以去問(wèn)微軟的OS開(kāi)發(fā)人員。
3、HINSTANCE
HINSTANCE也是一個(gè)32無(wú)符號(hào)整數(shù),它表示程序加載到內(nèi)存中的基地址。
51、static成員函數(shù)
因?yàn)閟tatic成員不是任何對(duì)象的組成部分,所以static成員函數(shù)不能被聲明為const。畢竟,將成員函數(shù)聲明為const就是承諾不會(huì)修改該函數(shù)所屬的對(duì)象。最后,static成員函數(shù)也不能被聲明為虛函數(shù)。
52、特殊的整型const static成員(P401)
const static數(shù)據(jù)成員在類的定義體中初始化時(shí),該數(shù)據(jù)成員仍必須在類的定義體之外進(jìn)行定義。
class Accout{
public:
static double rate() { return interestRate;}
static void rate(double); //sets a new rate
private:
static const int period = 30; //interest posted every 30 days
double daily_tbl[period]; // ok: period is constant expression
}
//definition of static member with no initializer;
//the initial value is specified inside the class definition
const int Accout::period;
但在gcc和MS vc++編譯器下似乎均不需要再次定義,也就是題設(shè)的“必須”二字在此失效。
53、操作符重載(P435)
下面是一些指導(dǎo)原則,有助于決定將操作符設(shè)置為類成員還是普通非成員函數(shù)
- 賦值(=)、下標(biāo)([])、調(diào)用(())和成員訪問(wèn)箭頭(->)等操作符必須定義為成員,將這些操作符定義為非成員函數(shù)將在編譯時(shí)標(biāo)記為錯(cuò)誤。
- 像賦值一樣,復(fù)合賦值操作符通常應(yīng)定義為類的成員。與賦值不同的是,不一定非得這樣做,如果定義非成員復(fù)合賦值操作符,不會(huì)出現(xiàn)編譯錯(cuò)誤。
- 改變對(duì)象狀態(tài)或與給定類型緊密聯(lián)系的其他一些操作符,如自增、自減和解引用,通常應(yīng)定義為類成員。
- 對(duì)稱的操作符,如算術(shù)操作符、相等操作符、關(guān)系操作符和位操作符,最好定義為普通非成員函數(shù)。
54、區(qū)別操作符的前綴和后綴形式(P447)
同時(shí)定義前綴式操作符和后綴式操作符存在一個(gè)問(wèn)題:它們的形參數(shù)目和類型相同,普通重載不能區(qū)別所定義的是前綴式操作符還是后綴式操作符。
為解決這一問(wèn)題,后綴式操作符函數(shù)接受一個(gè)額外的(即,無(wú)用的)int型形參。使用后綴操作符時(shí),編譯器提供0作為這個(gè)形參的實(shí)參。盡管我們的前綴式操作符函數(shù)可以使用這個(gè)額外的形參,但通常不應(yīng)該這樣做。那個(gè)形參不是后綴式操作符的正常工作所需要的,它的唯一目的是使后綴函數(shù)與前綴函數(shù)區(qū)別開(kāi)來(lái)。
55、顯式調(diào)用前綴式操作符
CheckedPtr parr(ia, ia+size); //ia points to an array of ints
parr.operator(0); //call postfix operator++
parr.operator(); //call prefix operator++
56、函數(shù)對(duì)象(P450)
struct absInt {
int operator() (int val){
return val<0 ? –val : val;
}
};
int i = –42;
absInt absObj; //object that defines function call operator
unsigned int ui = absObj(i); //calls absInt::operator(int)
盡管absObj是一個(gè)對(duì)象而不是函數(shù),我們?nèi)匀豢梢?#8220;調(diào)用”該對(duì)象,效果是運(yùn)行由absObj對(duì)象定義的重載調(diào)用操作符,該操作符接受一個(gè)int值并返回它的絕對(duì)值。
函數(shù)對(duì)象經(jīng)常用作通用算法的實(shí)參。(詳見(jiàn)P450)
57、函數(shù)對(duì)象的函數(shù)適配器(P453)
標(biāo)準(zhǔn)庫(kù)提供了一組函數(shù)適配器(function adapter),用于特化和擴(kuò)展一元和二元函數(shù)對(duì)象。函數(shù)適配器分為如下兩類:
(1)綁定器(binder),是一種函數(shù)適配器,它通過(guò)將一個(gè)操作數(shù)綁定到給定值而將二元函數(shù)對(duì)象轉(zhuǎn)換為一元函數(shù)對(duì)象。(bind1st和bind2nd 更多)
(2)求反器(negator),是一種函數(shù)適配器,它將謂詞函數(shù)對(duì)象的真值求反。(not1和not2 更多)
58、轉(zhuǎn)換操作符(P455)
轉(zhuǎn)換為什么有用?(詳見(jiàn)P454)
轉(zhuǎn)換函數(shù)采用如下通用形式:
operator type();
這里,type表示內(nèi)置類型名、類類型名或由類型別名所定義的名字。對(duì)任何可作為函數(shù)返回類型的類型(除了void之外)都可以定義轉(zhuǎn)換函數(shù)。一般而言,不允許轉(zhuǎn)換為數(shù)組或函數(shù)類型,轉(zhuǎn)換為指針(數(shù)據(jù)或函數(shù)指針)以及引用類型是可以的。
轉(zhuǎn)換函數(shù)必須是成員函數(shù),不能指定返回類型,并且形參表必須為空。
轉(zhuǎn)換函數(shù)一般不應(yīng)該改變被轉(zhuǎn)換的對(duì)象。因此,轉(zhuǎn)換操作符通常應(yīng)定義為const成員。
59、只能應(yīng)用一個(gè)類類型轉(zhuǎn)換
類類型轉(zhuǎn)換之后不能再跟另一個(gè)類類型轉(zhuǎn)換。如果需要多個(gè)類類型轉(zhuǎn)換,則代碼將出錯(cuò)。
假設(shè)有Integral=>SmallInt=>int,但是如果有一個(gè)函數(shù)cal(int),那么對(duì)于SmallInt si,可以使用cal(si),但對(duì)于Integral intVal;則不能使用cal(intVal)。語(yǔ)言只允許一次類類型轉(zhuǎn)換,所以該調(diào)用出錯(cuò)。
60、virtual與其他成員函數(shù)(P479)
C++中的函數(shù)調(diào)用默認(rèn)不使用動(dòng)態(tài)綁定。要觸發(fā)動(dòng)態(tài)綁定,必須滿足兩個(gè)條件:第一,只有指定為虛函數(shù)的成員函數(shù)才能進(jìn)行動(dòng)態(tài)綁定,成員函數(shù)默認(rèn)為非虛函數(shù),非虛函數(shù)不進(jìn)行動(dòng)態(tài)綁定;第二,必須通過(guò)基類類型的引用或指針進(jìn)行函數(shù)調(diào)用。
基類類型引用和指針的關(guān)鍵點(diǎn)在于靜態(tài)類型(static type,在編譯時(shí)可知的引用類型或指針類型)和動(dòng)態(tài)類型(dynamic type,指針或引用所綁定的對(duì)象的類型,這是僅在運(yùn)行時(shí)可知的)可能不同。
41、vector、list、deque的性能初窺
int large_size = 10000000;
cout_current_time("start init vector!\t");
vector<string> svec1(large_size, "Hello");
vector<string> svec2(large_size, "Hi");
cout_current_time("end init vector!\t");
cout_current_time("start init list!\t");
list<string> slist1(large_size, "Hello");
list<string> slist2(large_size, "Hi");
cout_current_time("end init list!\t");
cout_current_time("start init deque!\t");
deque<string> sdeq1(large_size, "Hello");
deque<string> sdeq2(large_size, "Hi");
cout_current_time("end init deque!\t");
用事實(shí)說(shuō)話最有說(shuō)服力:
start init vector! current time : 5:5:52
end init vector! current time : 5:5:55
start init list! current time : 5:5:55
end init list! current time : 5:6:14
start init deque! current time : 5:6:14
end init deque! current time : 5:6:26
可以看出大致時(shí)間比例為3/19/12。雖然不足以佐證它們的性能差距,但vector的常用似乎有了更充分的理由。
這里使用了一個(gè)簡(jiǎn)單的時(shí)間函數(shù)大致如下:
#include <time.h>
typedef struct tm * time_type;
time_type get_current_time(void) {
time_t t;
t = time(NULL);
return localtime(&t);
}
42、容器自增長(zhǎng)(P286)
每種實(shí)現(xiàn)都要求遵循以下原則:確保push_back操作高效地在vector中添加元素。從技術(shù)上來(lái)說(shuō),在原來(lái)為空的vector容器上n次調(diào)用push_back函數(shù),從而創(chuàng)建擁有n個(gè)元素的vector容器,其執(zhí)行時(shí)間永遠(yuǎn)不能超過(guò)n的常量倍。
43、類定義中為何不能具有自身的數(shù)據(jù)成員(P375)
因?yàn)橹挥挟?dāng)類定義體完成后才能定義類,因此類不能具有自身類型的數(shù)據(jù)成員。然而,只要類名一出現(xiàn)就可以認(rèn)為該類已聲明。因此,類的數(shù)據(jù)成員可以是指向自身類型的指針或引用:
class LinkScreen {
Screen window;
LinkScreen *next;
LinkScreen *prev;
};
44、兩種引用類類型的方法(P376)
Sales_item item1; //default initialized object of type Sales_item
class Sales_item item1; //equivalent definition of item1
兩種引用類類型的方法是等價(jià)的。第二種方法是從C繼承而來(lái)的,在C++中仍然有效。第一種更為簡(jiǎn)練,由C++語(yǔ)言引入,使得類類型更容易使用。
45、為什么類的定義以分號(hào)結(jié)束(P376)
分號(hào)是必須的,因?yàn)樵陬惗x之后可以接一個(gè)對(duì)象定義列表。定義必須以分號(hào)結(jié)束:
class Sales_item {/* … */};
class Sales_item {/* … */} accum, trans;
46、形參表和函數(shù)體處于類作用域中,函數(shù)返回類型不一定在類作用域中
在定義于類外部的成員函數(shù)中,形參表和成員函數(shù)體都出現(xiàn)在成員名之后。這些都是在類作用域中定義,所以可以不用限定而引用其他成員。因?yàn)樾螀⒈硎窃赟creen類作用域內(nèi),所以不必知名我們想要的是Screen::index。
如果返回類型使用由類定義的類型,則必須使用完全限定名。
#include "stdafx.h"
#include <iostream>
class MyClass
{
public :
typedef int index_t;
index_t twice(index_t in);
};
MyClass::index_t MyClass ::twice(index_t in)
{
return in * 2;
}
int _tmain(int argc, _TCHAR* argv[])
{
using namespace std;
MyClass obj;
MyClass::index_t x, y;
x = 10;
y = obj.twice(x);
cout<<"x = "<<x<<"; y = "<<y<<";"<<endl;
return 0;
}
47、構(gòu)造函數(shù)初始化式(P387)
與任意的成員函數(shù)一樣,構(gòu)造函數(shù)可以定義在類的內(nèi)部或外部。構(gòu)造函數(shù)初始化式只在構(gòu)造函數(shù)的定義中而不是聲明中指定。
構(gòu)造函數(shù)初始化列表難以理解的一個(gè)原因在于,省略初始化列表并在構(gòu)造函數(shù)的函數(shù)體內(nèi)對(duì)數(shù)據(jù)成員賦值是合法的。
在構(gòu)造函數(shù)初始化列表中沒(méi)有顯式提及的每個(gè)成員,使用與初始化變量相同的規(guī)則來(lái)進(jìn)行初始化。運(yùn)行該類型的默認(rèn)構(gòu)造函數(shù),來(lái)初始化類類型的數(shù)據(jù)成員。內(nèi)置或復(fù)合類型的成員的初始值依賴于對(duì)象的作用域:在局部作用域中這些成員不被初始化,而在全局作用域中它們被初始化為0。
如果那個(gè)類沒(méi)有默認(rèn)構(gòu)造函數(shù),則編譯器嘗試使用默認(rèn)構(gòu)造函數(shù)將會(huì)失敗。在這種情況下,為了初始化數(shù)據(jù)成員,必須提供初始化式。
對(duì)于這樣的成員,在構(gòu)造函數(shù)函數(shù)體中對(duì)它們賦值不起作用。沒(méi)有默認(rèn)構(gòu)造函數(shù)的類類型成員,以及const或引用類型的成員,不管是哪種類型,都必須在構(gòu)造函數(shù)初始化列表中進(jìn)行初始化。
因?yàn)閮?nèi)置類型的成員不進(jìn)行隱式初始化,所以對(duì)這些成員是進(jìn)行初始化還是賦值似乎都無(wú)關(guān)緊要。除了兩個(gè)例外,對(duì)非類類型的數(shù)據(jù)成員進(jìn)行賦值或使用初始化式在結(jié)果和性能上都是等價(jià)的。
48、成員初始化的次序
構(gòu)造函數(shù)初始化列表僅指定用于初始化成員的值,并不指定這些初始化執(zhí)行的次序。成員被初始化的次序就是定義成員的次序。
class X{
int i;
int j;
public:
//run-time error: i is initialized before j
X(int val): j(val), i(j) {}
}
在這種情況下,構(gòu)造函數(shù)初始化列表看起來(lái)似乎是用val初始化j,然后再用j來(lái)初始化i。然而i首先被初始化。這個(gè)初始化列表的效果是用尚未初始化的j值來(lái)初始化i!
49、使用默認(rèn)構(gòu)造函數(shù)(P393)
常犯的一個(gè)錯(cuò)誤是采用以下方式聲明一個(gè)用默認(rèn)構(gòu)造函數(shù)初始化的對(duì)象:
Sales_item myobj();
Sales_item myobj(); //ok: but defines a function, not an object
if(myobj.same_isbn(Primer_3rd_ed)) // error: myobj is a function
正確的方式應(yīng)該是去掉相應(yīng)的括號(hào):
Sales_item myobj;
或者
Sales_item myobj = Sales_item();
50、顯式或隱式初始化
#include "stdafx.h"
#include <iostream>
using namespace std;
class MyClass
{
public :
typedef int index_t;
bool same_object(MyClass obj);
public :
MyClass(int default_index = 5)
:default_index(default_index),
m_name("default_name"){}
MyClass::MyClass(std::string name);
public :
int default_index;
std::string m_name;
};
MyClass::MyClass(std::string name)
:default_index(0), m_name(name){}
bool MyClass::same_object(MyClass obj)
{
cout<<"m_name = "<<m_name.c_str()<<endl;
cout<<"obj.m_name = "<<obj.m_name.c_str()<<endl;
return strcmp(obj.m_name.c_str(), m_name.c_str()) == 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
MyClass obj;
cout<<"explicit : "<<obj.same_object(MyClass("default_name"))<<endl;
cout<<"implicit : "<<obj.same_object(string("default_name"))<<endl;
return 0;
}
因?yàn)榫哂幸詓td::string為形參的構(gòu)造函數(shù),因此在調(diào)用需要MyClass對(duì)象的same_object成員函數(shù)時(shí),會(huì)自動(dòng)隱式調(diào)用該構(gòu)造函數(shù)構(gòu)建MyClass對(duì)象,用于操作。但生成的MyClass對(duì)象是臨時(shí)對(duì)象,在same_object函數(shù)調(diào)用完成后銷毀。如果為了避免產(chǎn)生隱式轉(zhuǎn)換可以使用explicit關(guān)鍵字來(lái)抑制由構(gòu)造函數(shù)定義的隱式轉(zhuǎn)換:
