Day3
C++是个多重范型编程语言,我们可以将C++视为多个次语言组成的联邦
主要的次语言如下:
C
C语言有局限:没有模板 没有异常 没有重载
Object-Oriented C++
封装 继承 多态 virtual函数
Template C++
C++泛型编程
STL
当我们做出
#define ASPECT_RATIO 1.653
记号名称ASPECT_RATIO可能无法被编译器看见,解决之道是以一个常量替代宏
const double AspectRatio=1.653;
有两种特殊情况值得一提
第一种是定义常量指针,由于常量定义式常常被放在头文件中(方便被不同的源码含入),有必要将指针声明为const(非指针所指之物)
第二是class专属常量,为了将常量的作用域限制于class内,就必须让常量成为class的成员,而为保证常量至多一份实体,则必须让其成为一个static成员:
class GamePlayer{
private:
static const int NumTurns=5;//常量声明式
int scores[NumTurns];//使用该常量
通常类似说c++需要为使用的任何东西提供定义式,但如上面代码所示,它是个class专属常量又是static且为整数类型,则需特殊处理。只要不取地址,就可以声明它而无需提供定义式。若必须有定义式,则另外提供如下代码:
const int GamePlayer::NumTurns;//放入实现文件,不再设初值
enum类型
一个属于枚举类型的数值可权充ints被使用
class GamePlayer{
private:
enum{NumTurns=5};//令NumTurns成为5的一个记号名称
int scores[NumTurns];
...
};
如果你不想让其他人获得一个指针指向你的常量,就可以使用enum实现约束
它是模板元编程的基础技术
template inline函数
inline函数,即内联函数,内嵌函数
是将函数代码放入符号调用表,编译时直接展开,是一种以空间换时间的做法
template<typename T>
inline void callWithMax(const T&a,const T&b)
{
f(a>b?a:b);
}
这个template产出一群函数。callWithMax是一个真正的函数,遵守作用域和访问规则
有了这三个帮助,我们对预处理器的需求降低了
对于常量,我们尽量以const或者enum替换#defines
对于形似函数的宏,最好用inline函数替换#defines
尽可能使用const
关于const,我们可以在class外部修饰global或者namespace作用域中的常量,或者用来修饰函数、文件中被声明为static的对象。我们还可以用它修饰class内部的static和non-static成员变量
对于指针,指针自身和指针所指物都可以声明为const
char greeting[]="Hello";
char* p=greeting;
const char* p=greeting;//指针所指物为const,指针非const
char* const p=greeting;//指针为const
const char* const p=greeting;//两者都是const
注意:
const widget* pw与widget const* pw意义相同
const最具威力的用法是面对函数声明时的应用,它可以和函数的返回值、参数和函数自身(成员函数)产生关联
令函数返回一个常量值,往往能够降低因客户错误而造成的意外,又不至于放弃安全性和高效性
如有理数的operator*声明式:
class Rational{...};
const Rational operator*(const Rational& lhs,const Rational& rhs);
为什么要这样声明函数?当我们判断等式a*bc时,我们经常无意间犯下将“”写为“=”的错误,导致进行了赋值操作,有可能曹成严重的后果,如果我们将返回类型定义为const可避免这种低级错误。
const可实施于成员函数,这是为了确认该成员函数可作用于const对象上。这类成员函数使class接口比较容易理解,因为可以得知哪个函数可以改动对象内容而哪个对象不可以,并且它们使操作“const对象”成为可能
若两个成员函数只是在常量性有所不同,那么也可以重载,示例如下:
class TextBlock{
public:
...
const char& operator[](std::size_t position)const
{return text[position];}
char& operator[](std::size_t position)
{return text[position];}
private:
std::string text;
};
可以看到我们在上述代码中定义了两次operator[]函数
TextBlock tb("Hello");
std::cout<<tb[0];//调用的是non-const的operator函数
tb[0]='x';//写一个non-const TextBlock
const TextBlock ctb("World");
std::cout<<ctb[0];//调用的是const的operator函数
ctb[0]='x';//错误!不能写一个const TextBlock
确定对象在使用前被初始化
读取未初始化的对象会导致不明确行为,针对这个问题,最佳处理方法是:永远在对象使用之前将其初始化
对于内置类型,必须手工进行初始化
int x=0;
const char* text="A C-style String";
double d;
std::cin>>d;//读取input stream完成初始化
对于其他类型,初始化的责任在构造函数
赋值操作和初始化操作有很大的差别,考虑一个表现通讯录形式的class
class PhoneNumber{...};
class ABEntry{
public:
ABEntry(const std::string name,const std::string& address,
const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
ABEntry::ABEntry(const std::string name,const std::string& address,
const std::list<PhoneNumber>& phones);
{
theName=name;//赋值,而非初始化
theAddress=address;
thePhones=phones;
numTimesConsulted=0;
}
C++规定,对象的成员变量的初始化发生在进入构造函数本体之前,ABEntry函数的更好的写法使使用成员初值列替换赋值动作
ABEntry::ABEntry(const std::string name,const std::string& address,
const std::list<PhoneNumber>& phones);
:theName(name),
theAddress(address),
thePhones(phones),
numTimesConsulted(0)
{}//构造函数本体不必有任何操作
若使用赋值方法,default构造函数的一切就浪费了
总是在初值列中列出所有成员变量
如果成员变量是const或是references,它们就一定需要初值,不能被赋值
C++有着十分固定的初始化次序,先是基类base class被初始化,然后是派生类derived class,而class的成员变量总是以声明次序被初始化。初始化array时需要指定大小,因此代表大小的成员变量必须先有初值
下面有些不太懂