C++
为一个语言联邦一开始C++
只是C加上一些面向对象特性,但是随着这个语言的成熟他变得更加无拘无束,接受不同于C with classes
的各种观念、特性和编程战略。异常对函数的结构化带来了不同的做法,templates
将我们带来到新的设计思考方式,STL
则定义了一个前所未见的伸展性做法。
今天C++
已经是个多重范型编程语言,一个同时支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式的语言。这些能力和弹性使C++
成为一个无可匹敌的工具,因此、将C++
视为一个语言联邦。
cosnt、enum、inline
替换#define
因为、宏定义会被预处理器处理,编译器并未看到宏定义的信息,当出现一个编译错误信息的时候,可能会带来困惑。
解决之道就是使用一个常量替换宏定义(#define
)
const double AspectRatio = 1.653; // 大写名称通常代表宏定义,因此这里可以使用首字母大写的方法表示const全局变量
作为一个语言常量,AspectRatio
肯定会被编译器看到,当然就会进入符号表内。另外、使用常量也可以有较小的码、因为使用预处理会导致预处理器盲目的将宏名称替换为对应的数值,可能会导致目标码出现多份宏定义的数值。
基于数个理由enum hack
值得我们认识。
class GamePlayer{ private: enum {NumTurns = 5}; // enum hack 令NumTurns成为5的一个标记 int scores[NumTurns]; // };
enum hack
的行为某方面来说比较像#define
而不像const
,有的时候这正是你想要的,例如取一个const
的地址是合法的,但是取一个enum
的地址就是不合法的,而取一个#define
的地址通常也不合法。如果你不想让别人获得一个pointer
或者reference
指向你的某个整数常量,enum
可以帮助你实现这个约束。const
对象设置存储空间,但是不够优秀的编译器可能会设置另外的储存空间,enum
和#define
一样绝对不会导致非必要的内存分配。对于单纯的常量,最好以const
对象或者enums
替换#define
对于形似函数的宏(macros
),最好改用inline
函数替换#define
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
const
语法虽然变化多端,但并不是莫测高深,如果关键字const
出现在型号的左边,表示被指物是常量,如果出现在星号的右边,表示指针自身是常量,如果出现在星号两边,表示被指物和指针两者都是常量。
如果被指物是常量,有些程序员会将关键字const
写在类型之前,有些人会把它写在类型之后、星号之前,这两种写法的意义相同,所以下列两个函数的参数类型是一样的:
void f(const Widget* pw); // 一个指向常量的指针 void f2(Widget const* pw); // 一个指向常量的指针
两种形式都有人使用,是否是指向常量的指针,要看const
相对于星号的位置,星号左边为指向常量的指针,星号右边为常量指针。
const
修饰函数返回值,可以降低编码出现的低级错误
class Rational {}; const Rational operator*(const Rational& lhs, const Rational& rhs); Rational a, b, c; if (a*b = c) // 其实是想做个比较,当operator*返回值声明为const的时候将会返回错误,也就防止了编码不小心带来的异常
const
修饰成员函数
const
得知哪些函数可以改动对象内容,哪些函数不可以const
对象成为可能关于将变量初始化这件事,C++
似乎总是反复无常。但是有一点是可以确定的是,读取没有初始化的值会导致不确定行为
C++
默默编写并调用哪些函数什么时候empty class
不再是个空类呢?当C++
处理过之后,是的,如果你没有自己声明,并一起就会为它声明(编译器版本)一个copy
构造函数、一个copy assignment
操作符和一个析构函数。
因此、如果你声明了一个empty class
如下:
class Empty{};
编译器处理之后就好像你写了如下的代码:
class Empty { public: Empty() {} // default构造函数 Empty(const Empty& rhs) {} // copy构造函数 ~Empty() {} //析枸函数 Empty& operator=(const Empty& rhs) {} // copy assignment 操作符 };
唯有当这些函数被需要(被调用),它们才会被编译器创建出来。
好了,我们知道编译器会常见这些函数,但这些函数做了什么?default
构造函数和析构函数,主要是给编译器一个地方放置藏在幕后的代码,像是调用base class
和non-static
成员变量的构造函数和析构函数。需要注意的是编译器默认的析构函数是non-virtual
的。
有时你不想让用户使用某个函数,不对函数进行声明就行了。但是这样做对copy
构造函数和copy assignment
操作符却不起作用,因为、如果你不进行声明,编译器会声明一个默认的出来。
这就把你逼到一个困境,如果你不想让用户使用copy
构造函数和copy assignment
函数,你既不能不声明也不能进行声明。这个问题的解决方案就是,将函数声明为私有的函数,这样你即可以阻止编译器创建它们,又因为是私有函数,使得别人不能调用。
但是这样做并不是绝对安全的,因为member
函数和friend
函数还是可以调用private
函数的。除非你足够聪明不去定义它们,那么如果任何人不慎调用了任何一个函数,将会导致一个链接错误,将成员函数声明为私有,而又故意不去实现它们是如此的受欢迎。、
class HomeForSale { public: ... private: HomeForSale(const HomeForSale&); // 因为根本没有人能调用,写参数名称也是浪费 HomeForSale& operator=(const HomeForSale&); };
有了上述的定义之后,当用户企图调用拷贝HomeForSale
对象的时候,编译器会阻止他,如果不慎在member
或者friend
函数中调用,连接器也会发出抱怨。
为了驳回编译器自动提供的功能,可将相应的成员函数声明为private
并且不予实现。