#
1、const其实并不是真正的常量(P32)
2、早期的gets()中的Bug导致了Internet蠕虫(P42)
gets()函数并不检查缓冲区的空间,事实上它也无法检查缓冲区的空间。如果函数的调用者提供了一个指向堆栈的指针,并且gets()函数读入的字符数量超过缓冲区的空间,gets()函数将会愉快地将多出来的字符继续写入到堆栈中,这就覆盖了堆栈原先的内容。——这就是病毒利用它来写入额外空间,并引发蠕虫病毒的前提。
推荐的方式是将
gets(line)
替换为
if(fgets(line, sizeof(line), stdin) == NULL)
exit(1);
3、相邻字符串常量自动连接(P45)
这个其实已经应用很普遍了,但是我个人用的比较少,特此记录一下。
4、返回一个指针?(P48)
这个话题围绕一个程序的BUG来展开,这个程序返回了局部变量的值的指针,这么说当然你一眼就能看得出来问题所在,但是在很多时候,这个错误却总是在你的眼皮子底下溜走。
作者提供了五种方式,只能说可以用,但唯一推荐的只有一个,详见作者的分析(P48)(不是什么高深的理论,你自己也能分析地出来)。
a.返回一个字符串常量的指针。因为常量存在静态数据存储区,所以指针没问题。
b.使用全局声明的数组。提到全局两个字,就知道这个方法有很大的局限性。
c.使用静态数组。下一次调用将覆盖这个数组内容。
char * func() {
static char buffer[20];
…
return buffer;
}
d.显式分配一些内存,保存返回的值。
char * func() {
char * s = malloc(120);
…
return s;
}
既然用到了malloc,就必然伴随着free,因此带来了内存管理的问题,增加了开发者负担。
e.(推荐)在调用前后,由函数调用者分配内存,并由其释放,在同一地方释放对于内存管理来说代价相对最小。
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);
快速排序法:(好土,感觉满世界都会,不过还是写一下,当然了,标准库里多的是排序算法),这里还是实现经典版的快速排序了,时间复杂度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;
}
用法:(顺便有标准库的排序法,当然只是调一下,没有什么可说的了)
#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;
}
以上内容摘自《编程之美》P150-154。
为了方便使用,下面是可拷贝的代码:
Math.h
#pragma once
class Math
{
public:
Math(void);
~Math(void);
public :
//编程之美P150-154
//求最大公约数,欧几里德——辗转相除法
static int Gcd1(int x, int y);
//求最大公约数,欧几里德——辗转相除法(变相将除法变成了减法)
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顺序不能错;
return y ? Gcd1(y, x % y) : x;
}
int Math::Gcd2(int x, int y)
{
//与Gcd1相同的方式,但由于x%y计算速度较x-y要慢,但效果相同,所以换用x - y
// 但用减法和除法不同的是,比如和,%20=10,-20=70,也就是-4×=10
// 也就是说迭代次数较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;
}
不过有一点值得一提,就是所谓性能最好效率最高的Gcd3不支持负数,也就是最后两行测试代码无法通过。但是限于对负数的最大公约数并没有定义,也就是说即便上面的Gcd1和Gcd2好像算出了负数,但它们的结果没有意义。
#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看似缭乱却是一个交换函数:
从最后一次做*a和*b的位置开始向上:
也就是*b = *a - *b;扩展为*b = (*a + *b) - *b展开就是*b = *a;也就是将*a的值赋给了*b。(注意到在此之前*b从未改变过)
*a = *a - *b(这时候不能用上一行的结论,因为*a曾经改变过了)扩展为*a = (*a + *b) – *a 也就是*a = *b,这里*b是指原始的*b,而不是上一行的结论,至此就交换完毕。
可以注意到,这里并没有使用临时变量。所以这是一个不需要临时变量的交换方法。不过这种方法只支持支持operator+和operator-的数值计算(基本上只能用在整数上,因为对浮点的操作可能涉及到舍入的问题)
而且这个方法还有一个缺陷,就是对数值边界的判断,比入MAX_INT+MAX_INT就溢出了,所以它存在一定的局限性。
如图所示的代码出现了如图所示的错误,谁能解释一下是为什么呢?
虽然在最后include进了cpp文件,而且这种做法也在C++ Primer中也是正确的(难道是标准和现实的差距?)。将代码稍微变动,并将cpp部分的内容移到.h文件中的include位置即可正确编译。
在C++中我们应该少用指针,多用引用,原因请大家自行搜索。在传递数组的时候我们需要格外注意,先让我们看一个简单的范例。
// PassArray.cpp : 定义控制台应用程序的入口点。
//
#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;
}
首先这个范例无法编译通过:
原因就出在类型推断上。根据定义,Func2的类型必须是T&,也就是说传递实参的时候,两个形参必须是相同的,而这一点在模板编程中就会由编译器来负责推断。
Func1:
调用Func1(a, b)则推断的类型分别是Func1(int*, int*),调用函数将会自动将数组的首地址指针作为实参进行传递,因此类型推断两形参相同,编译通过!
Func2:
调用Func2(a, b)因为我们希望按引用的方式进行实参传递,因此需要遵循这样的规律:
(P208)如果形参是数组的引用,编译器将不会将数组实参转化为指针,而是传递数组引用的本身。在这种情况下,数组大小成为形参和实参类型的一部分。
所以推断类型分别是Func2(int (&)[10], int (&)[12]),因为int (&)[10] != int (&)[12],所以与T == T相悖!自然也就编译不过了!
Func3:
该函数是Func2的一个静态表示,通过上面的解释应该很容易理解这个代码了。
这不是一个面向对象的代码库,它的存在仅仅只是为了说明几个函数调用,如果要在您的工程中应用相关内容,请自行构建(这应该不难),或者看看我推荐的文档。
// ProcessAffinity.cpp : 定义控制台应用程序的入口点。
//
#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返回一个常量,代表当前的进程句柄
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;
}
推荐文档:《使用增强的计时器测量代码段》(下载可适合打印,已排版,阅读该文档需要使用Microsoft Word 2007或相关阅读器(后缀docx),如您需要pdf文档,可以给我留言,我会将它发给你,在CSDN的下载中,您可能需要注册成为相关网站的会员,或者使用积分,如果您觉得麻烦,可以直接向我索取!点此获取!)
关键字:句柄, HANDLE, WINDOWS SDK, windows.h,
从广义上,能够从一个数值拎起一大堆数据的东西都可以叫做句柄。句柄的英文是"Handle",本义就是"柄",只是在计算机科学中,被特别地翻译成"句柄",其实还是个"柄"。从一个小东西拎起一大堆东西,这难道不像是个"柄"吗?
然后,指针其实也是一种"句柄",只是由于指针同时拥有更特殊的含义——实实在在地对应内存里地一个地址——所以,通常不把指针说成是"句柄"。但指针也有着能从一个32位的值引用到一大堆数据的作用,这不是句柄又是什么?
Windows系统中有许多内核对象(这里的对象不完全等价于"面向对象程序设计"一词中的"对象",虽然实质上还真差不多),比如打开的文件,创建的线程,程序的窗口,等等。这些重要的对象肯定不是4个字节或者8个字节足以完全描述的,他们拥有大量的属性。为了保存这样一个"对象"的状态,往往需要上百甚至上千字节的内存空间,那么怎么在程序间或程序内部的子过程(函数)之间传递这些数据呢?拖着这成百上千的字节拷贝来拷贝去吗?显然会浪费效率。那么怎么办?当然传递这些对象的首地址是一个办法,但这至少有两个缺点:
暴露了内核对象本身,使得程序(而不是操作系统内核)也可以任意地修改对象地内部状态(首地址都知道了,还有什么不能改的?),这显然是操作系统内核所不允许的;
操作系统有定期整理内存的责任,如果一些内存整理过一次后,对象被搬走了怎么办?
所以,Windows操作系统就采用进一步的间接:在进程的地址空间中设一张表,表里头专门保存一些编号和由这个编号对应一个地址,而由那个地址去引用实际的对象,这个编号跟那个地址在数值上没有任何规律性的联系,纯粹是个映射而已。
在Windows系统中,这个编号就叫做"句柄"。
Handle在Windows中的含义很广泛,以下关于谈到的Handle除非特别说明,将仅限于进程、线程的上下文中。
1、先来谈谈Handle
Handle本身是一个32位的无符号整数,它用来代表一个内核对象。它并不指向实际的内核对象,用户模式下的程序永远不可能获得一个内核对象的实际地址(一般情况下)。那么Handle的意义何在?它实际上是作为一个索引在一个表中查找对应的内核对象的实际地址。那么这个表在哪里呢?每个进程都有这样的一个表,叫句柄表。该表的第一项就是进程自己的句柄,这也是为什么你调用GetCurrentProcess()总是返回0x7FFFFFFF原因。
简单地说,Handle就是一种用来"间接"代表一个内核对象的整数值。你可以在程序中使用handle来代表你想要操作的内核对象。这里的内核对象包括:事件(Event)、线程、进程、Mutex等等。我们最常见的就是文件句柄(file handle)。
另外要注意的是,Handle仅在其所属的进程中才有意义。将一个进程拥有的handle传给另一个进程没有任何意义,如果非要这么做,则需要使用DuplicateHandle(),在多个进程间传递Handle是另外一个话题了,与这里要讨论的无关。
2、进程ID
首先,进程ID是一个32位无符号整数,每个进程都有这样的一个ID,并且该ID在系统范围内是唯一的。系统使用该ID来唯一确定一个进程。
深入些说,系统可能使用进程ID来计算代表该进程的内核对象的基地址(及EPROCESS结构的基地址),具体的计算公式你可以去问微软的OS开发人员。
3、HINSTANCE
HINSTANCE也是一个32无符号整数,它表示程序加载到内存中的基地址。
51、static成员函数
因为static成员不是任何对象的组成部分,所以static成员函数不能被声明为const。毕竟,将成员函数声明为const就是承诺不会修改该函数所属的对象。最后,static成员函数也不能被声明为虚函数。
52、特殊的整型const static成员(P401)
const static数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义。
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++编译器下似乎均不需要再次定义,也就是题设的“必须”二字在此失效。
53、操作符重载(P435)
下面是一些指导原则,有助于决定将操作符设置为类成员还是普通非成员函数
- 赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。
- 像赋值一样,复合赋值操作符通常应定义为类的成员。与赋值不同的是,不一定非得这样做,如果定义非成员复合赋值操作符,不会出现编译错误。
- 改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常应定义为类成员。
- 对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。
54、区别操作符的前缀和后缀形式(P447)
同时定义前缀式操作符和后缀式操作符存在一个问题:它们的形参数目和类型相同,普通重载不能区别所定义的是前缀式操作符还是后缀式操作符。
为解决这一问题,后缀式操作符函数接受一个额外的(即,无用的)int型形参。使用后缀操作符时,编译器提供0作为这个形参的实参。尽管我们的前缀式操作符函数可以使用这个额外的形参,但通常不应该这样做。那个形参不是后缀式操作符的正常工作所需要的,它的唯一目的是使后缀函数与前缀函数区别开来。
55、显式调用前缀式操作符
CheckedPtr parr(ia, ia+size); //ia points to an array of ints
parr.operator(0); //call postfix operator++
parr.operator(); //call prefix operator++
56、函数对象(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是一个对象而不是函数,我们仍然可以“调用”该对象,效果是运行由absObj对象定义的重载调用操作符,该操作符接受一个int值并返回它的绝对值。
函数对象经常用作通用算法的实参。(详见P450)
57、函数对象的函数适配器(P453)
标准库提供了一组函数适配器(function adapter),用于特化和扩展一元和二元函数对象。函数适配器分为如下两类:
(1)绑定器(binder),是一种函数适配器,它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。(bind1st和bind2nd 更多)
(2)求反器(negator),是一种函数适配器,它将谓词函数对象的真值求反。(not1和not2 更多)
58、转换操作符(P455)
转换为什么有用?(详见P454)
转换函数采用如下通用形式:
operator type();
这里,type表示内置类型名、类类型名或由类型别名所定义的名字。对任何可作为函数返回类型的类型(除了void之外)都可以定义转换函数。一般而言,不允许转换为数组或函数类型,转换为指针(数据或函数指针)以及引用类型是可以的。
转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。
转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为const成员。
59、只能应用一个类类型转换
类类型转换之后不能再跟另一个类类型转换。如果需要多个类类型转换,则代码将出错。
假设有Integral=>SmallInt=>int,但是如果有一个函数cal(int),那么对于SmallInt si,可以使用cal(si),但对于Integral intVal;则不能使用cal(intVal)。语言只允许一次类类型转换,所以该调用出错。
60、virtual与其他成员函数(P479)
C++中的函数调用默认不使用动态绑定。要触发动态绑定,必须满足两个条件:第一,只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定;第二,必须通过基类类型的引用或指针进行函数调用。
基类类型引用和指针的关键点在于静态类型(static type,在编译时可知的引用类型或指针类型)和动态类型(dynamic type,指针或引用所绑定的对象的类型,这是仅在运行时可知的)可能不同。
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");
用事实说话最有说服力:
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
可以看出大致时间比例为3/19/12。虽然不足以佐证它们的性能差距,但vector的常用似乎有了更充分的理由。
这里使用了一个简单的时间函数大致如下:
#include <time.h>
typedef struct tm * time_type;
time_type get_current_time(void) {
time_t t;
t = time(NULL);
return localtime(&t);
}
42、容器自增长(P286)
每种实现都要求遵循以下原则:确保push_back操作高效地在vector中添加元素。从技术上来说,在原来为空的vector容器上n次调用push_back函数,从而创建拥有n个元素的vector容器,其执行时间永远不能超过n的常量倍。
43、类定义中为何不能具有自身的数据成员(P375)
因为只有当类定义体完成后才能定义类,因此类不能具有自身类型的数据成员。然而,只要类名一出现就可以认为该类已声明。因此,类的数据成员可以是指向自身类型的指针或引用:
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
两种引用类类型的方法是等价的。第二种方法是从C继承而来的,在C++中仍然有效。第一种更为简练,由C++语言引入,使得类类型更容易使用。
45、为什么类的定义以分号结束(P376)
分号是必须的,因为在类定义之后可以接一个对象定义列表。定义必须以分号结束:
class Sales_item {/* … */};
class Sales_item {/* … */} accum, trans;
46、形参表和函数体处于类作用域中,函数返回类型不一定在类作用域中
在定义于类外部的成员函数中,形参表和成员函数体都出现在成员名之后。这些都是在类作用域中定义,所以可以不用限定而引用其他成员。因为形参表是在Screen类作用域内,所以不必知名我们想要的是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、构造函数初始化式(P387)
与任意的成员函数一样,构造函数可以定义在类的内部或外部。构造函数初始化式只在构造函数的定义中而不是声明中指定。
构造函数初始化列表难以理解的一个原因在于,省略初始化列表并在构造函数的函数体内对数据成员赋值是合法的。
在构造函数初始化列表中没有显式提及的每个成员,使用与初始化变量相同的规则来进行初始化。运行该类型的默认构造函数,来初始化类类型的数据成员。内置或复合类型的成员的初始值依赖于对象的作用域:在局部作用域中这些成员不被初始化,而在全局作用域中它们被初始化为0。
如果那个类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。在这种情况下,为了初始化数据成员,必须提供初始化式。
对于这样的成员,在构造函数函数体中对它们赋值不起作用。没有默认构造函数的类类型成员,以及const或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。
因为内置类型的成员不进行隐式初始化,所以对这些成员是进行初始化还是赋值似乎都无关紧要。除了两个例外,对非类类型的数据成员进行赋值或使用初始化式在结果和性能上都是等价的。
48、成员初始化的次序
构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序。成员被初始化的次序就是定义成员的次序。
class X{
int i;
int j;
public:
//run-time error: i is initialized before j
X(int val): j(val), i(j) {}
}
在这种情况下,构造函数初始化列表看起来似乎是用val初始化j,然后再用j来初始化i。然而i首先被初始化。这个初始化列表的效果是用尚未初始化的j值来初始化i!
49、使用默认构造函数(P393)
常犯的一个错误是采用以下方式声明一个用默认构造函数初始化的对象:
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
正确的方式应该是去掉相应的括号:
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;
}
因为具有以std::string为形参的构造函数,因此在调用需要MyClass对象的same_object成员函数时,会自动隐式调用该构造函数构建MyClass对象,用于操作。但生成的MyClass对象是临时对象,在same_object函数调用完成后销毁。如果为了避免产生隐式转换可以使用explicit关键字来抑制由构造函数定义的隐式转换: