《C++ Prime Plus》学习总结

1.      内联函数

(1)      内联函数是C++为提高程序运行速度所做的一项改进。

(2)      内联函数的编译代码与其他程序代码“内联”起来了,也就是说,编译器将使用相应的函数代码替代函数调用。

(3)      内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。比如,如果程序在10个不同地方调用同一个内联函数,则该程序将包含该函数代码的10个副本。

(4)      使用内联函数这项特性,必须在函数声明前加上关键字inline或在函数定义前加上关键字inline。但通常的做法是将整个定义放在本应提供原型的地方。(在函数首次使用前出现的整个函数定义充当了原型。)

(5)      程序员请求将函数作为内联函数时,编译器并不一定会满足这种要求,它可能认为该函数过大或注意到函数调用了自己。

(6)      内联函数这种特性,在C语言中存在类似的特性,C语言使用预处理器语句#define来提供宏(宏,是内联代码的原始实现。)。内联和宏的区别:宏只是通过文本替换来实现,所以,不能实现按值传递;而内联可以按值传递。

如下宏:

#define SQUARE(X) (X)*(X)

调用:a = SQUARE(5.0);  b =SQUARE(4.5+7.5);  d = SQUARE(c++);//会将c递增两次。

(7)      在有些地方,必须在类外定义内敛函数,比如,友元关系中存在的循环依赖。解决的办法:使用前向声明、内敛函数在类外某个友元类之后定义。

2.      C++新增了一种复合类型,引用变量。引用是已定义的变量的别名(另一个名称)。

(1)      引用变量的主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是其副本。因此,除指针之外,引用也为函数处理大型结构提供了一种非常方便的途径。

(2)      就像声明中的char*指的是指向char的指针一样,int&指的是指向int的引用。

int rats;

int& rodents = rats;

int& doo = redents;

上述三个rats、rodents和doo可以互换使用,因为它们指向相同的值和内存单元。如果通过取地址符&对三者操作,它们的地址是一样的。

(3)      引用看上去很像伪装表示的指针,举例:

int rats = 101;

int& rodents = rats;

int* prats = &rats; //等价于int& prats =&rodents;

这样,表达式rodents和*prats都可以同rats互换,而表达式&rodents和prats都可以同&rats互换。

假设,*解除引用运算符被隐式的进行,引用看上去很像伪装表示的指针。

(4)      必须在声明引用时将其初始化,而不能像指针那样,先声明,再赋值。于是,引用更接近const指针,必须在创建时进行初始化,一旦与某个变量关联起来,就将一直效忠于它。

int& rodents = rats;

int * const pr = &rats;

其中,引用rodents扮演的角色与表达式*pr相同。

(5)      引用作为函数参数应该注意的地方:

a.      比如,double x = 3.0;

函数原型 double refcube(double &ra);

函数调用 double z =refcube(x + 3.0);

在有些较老的编译器会勉强通过,但会发出警告。在较老的编译器是这样对待的:程序创建一个double类型的临时无名变量,它的值是表达式x+3.0的值,然后,ra将成为该临时变量的引用。

在现代C++中,上述调用时错误的,通过不了编译。原因是表达式x+3.0并不是变量,例如,不能对表达式赋值,如:x+3.0 = 5.0 。也就是说,它不是左值。

b.      当且仅当函数参数为const引用时,当实参与引用参数不匹配但可以转换为正确的类型,或者实参与引用参数匹配但不是左值,C++允许生成临时变量。

c.       实际上,对于形参为const引用的C++函数,如果实参不匹配,则其行为类似于按值传递(确保原始数据不被修改,将使用临时变量来存储值)。

3.      将引用参数声明为常量数据的引用的理由有三个:

(1)      使用const可以避免无意中修改数据的编程错误。

(2)      使用const使函数能够处理const和非const实参,否则,只能将只能接受非const数据。

(3)      使用const引用使函数能够正确生成并使用临时变量。

4.      非const引用参数(常规引用参数)作为函数的参数时,实参与形参的传递过程中,编译器不会创建临时变量。而const引用参数作为函数的参数时,需不需要创建临时变量根据具体情况而定(1.当实参的类型正确,但不是左值。2.当实参的类型不正确,但可以转换为正确的类型。符合这两条就需要创建临时变量,否则不必创建,直接使用实参别名)。

5.      引用作为函数的返回类型,实际上返回的是被引用的变量的别名。常规(非引用)函数返回类型是右值(不能通过地址返回的值),这种返回值位于临时内存单元中,运行到下一条语句时,它们可能不再存在。函数返回引用常规的做法是返回一个作为参数传递给函数的引用,或者是,返回用new来分配新的存储空间的引用(当不需要时,应该delete释放)。

6.      将引用用于类对象。将类对象传递给函数时,通常的做法是使用引用。传递类对象参数的标准方式是按引用传递。

7.      引用在类继承中的作用。基类的引用可以指向派生类对象,而无需进行强制类型转换。

8.      引用分为左值引用(&)和右值引用(&&)。右值引用是C++11新增的。

9.      默认参数

(1)      默认参数指的是当函数调用时省略的实参时自动使用的一个值。

(2)      函数调用时的默认参数值必须通过函数原型对其设置。在函数定义中设置是无效的。

(3)      带参数列表的函数,必须从右到左添加默认值。实参必须按从左到右的顺序依次被赋给相应的形参,而不能跳过任何参数。

(4)      函数重载:名称相同的函数,但它们的特征标不同。

决定函数的特征标的因素:参数数目、类型、参数排列顺序。这些因素是用来判断两个函数能否进行重载的依据。函数类型(返回类型)不属于这些因素范畴。是否带const能否作为判断依据呢?当类型是引用类型或者是指针类型时,是否带const可以作为判断依据,其他情况下不能。

注意:编译器将类型引用和类型本身视为同一个特征标(指针及其指针所指向的类型视为不同的特征标,因为调用时,可以找到最佳调用函数),于是下面两个原型:

double cube(double x);

double cube(double & x);

对上述函数进行调用时,出现错误:对重载函数的调用不明确。

10.  函数模板,是通用的函数描述,也就是说,它们使用泛型来定义函数。

如果需要对多个不同类型使用同一种算法的函数时,可以使用模板。并不是所有类型都使用同一种算法,这时可以考虑使用重载的模板。和常规重载一样,被重载的模板的函数特征标必须不同。例如下面两个重载的模板原型:

template <typename T> void Swap(T &a, T & b); 

template <typename T> void Swap(T *a, T *b, int n);

模板的局限性:

(1)      由于是使用泛型定义函数,所以不知道此类型是否支持函数模板中涉及的那些操作,比如,+、-、*、/等算术运算符和比较运算符等等。

(2)      针对某个具体类型,它的算法跟其他统一的算法有所区别(结构或功能)。无法使用函数模板重载方法实现(因为特征标相同,函数内部差别大,实现的功能不一样)。

为了缓解上述的局限性,C++为特定类型提供具体化的模板定义。

11.  函数模板的显示具体化

(1)      对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及它们的重载版本。注意:显示具体化模板函数可以有重载版本。

(2)      显示具体化的原型和定义应以template<>打头,并通过名称来指出类型。

(3)      具体化优先于常规模板,而非模板函数优先于具体化和模板函数。

举例,以下是用于交换job结构的非模板函数、模板函数和具体化的原型:

void Swap(job&, job &); //非模板函数,可以重载

template<typename T> void Swap( T &, T &); //模板函数,可以重载

template<> void Swap<job>(job &, job &); //具体化,可以重载

或 template <>void Swap(job &, job &);

上述三者可以同时出现在同一个文件中,因为有原则(3)

12.  函数模板的实例化

因为,函数模板不是函数定义,它只是一个用于生成函数定义的方案,所以,编译器使用模板为特定类型生成函数定义时,得到的是模板实例。

实例化有两种:隐式实例化和显示实例化

(1)      隐式实例化:函数调用Swap(i,j)时,导致编译器生成Swap()的一个实例,该实例使用int类型(假设i和j都是整型)。像这种,直到需要使用函数定义时,才为某具体类型生成函数定义,这种实例化方式称为隐式实例化。编译器之所以知道需要进行定义,是因为程序调用Swap()函数时提供了int参数。最初,编译器只支持隐式实例化,不支持显示实例化。

(2)      显示实例化:

a.      第一种,通过声明实现显示实例化。可以直接命令编译器创建特定的实例,而不是等到需要用函数定义时才创建实例。其声明语法:

template void Swap<int>(int, int);  //此为声明,而定义共用模板中的定义

有了此句,不管有没有函数调用Swap(i,j); int类型的Swap()函数都已经定义好了。

b.      第二种,通过使用函数来创建显示实例化。例如:

template <class T> T Add(T a, T b) { return a + b ;}

int m = 6;

double x = 10.2;

cout<<Add<double>(x,m)<<endl;

上面的模板与函数调用Add(x,m)不匹配,于是通过显示实例化,生成double类型的Add()函数定义,同时将参数m强制转换为double类型。

注意:隐式实例化、显示实例化和显示具体化统称为具体化(specialization)。它们的相同之处在于,都是使用具体类型的函数定义,而不是通用描述。

显示实例化的声明中使用了前缀template,而显示具体化的声明中template<>

警告:试图在同一个文件(或转换单元)中使用同一种类型的显示实例化和显示具体化将出错。

13.  重载解析

因为编译器需要知道选择使用哪个函数版本,这个过程称为重载解析。

重载解析将寻找最匹配的函数。如果只存在一个这样的函数,则选择它;如果存在多个这样的函数,但其中只有一个是非模板函数,则选择该函数;如果存在多个适合的函数,且它们都为模板函数,但其中有一个模板函数比其他模板函数更具体,则选择该函数。如果有多个同样合适的非模板函数(或模板函数),但没有一个函数比其他函数更具体,则函数调用将是不确定的,因此是错误的;当然,如果不存在匹配的函数,则也是错误的。

14.  重载解析是编译器自动完成的工作,而程序员在函数调用时可以自行选择需要的函数版本。

原型和声明:

template <class T> T lesser( T a, T b);

int lesser (int a, int b);

int m,n;

函数调用:lesser(m,n) 、lesser<>(m,n)和lesser<double>(m,n)的区别是,前者是使用编译器的自动重载解析功能来生成对应类型的lesser函数定义,然后调用;而中者是程序员强制让编译器使用模板函数来生成与参数类型一致的对应的类型的lesser函数定义并用调用;而后者也是程序员强制让编译器使用模板函数来生成double类型的lesser函数定义并调用。

15.  关键字decltype

为了解决两个未知类型的数据运算之后的结果是什么类型。

声明: decltype(expression) var;

比如:decltype(x+y) xpy;  //表示声明变量xpy,它的类型是表达式x+y对应的类型。

关键字decltype的工作原理,用以下简单步骤描述:

(1)      第一步,先判断expression是否是没有用括号括起来的一个标识符,如果是,则var的类型与该标识符的类型相同,如果该标识符的类型含const,则var类型也含有。

(2)      第二步,再判断expression是否是一个函数调用,如果是,则var类型与函数的返回类型相同。注意,并不会实际调用函数。

(3)      第三步,继续判断expression是否用括号括起的标识符,如果是,则var为指向其类型的引用。

举例:

double xx = 4.4;

decltype((xx)) r2 = xx; // r2 is double &

decltype(xx) w = xx; // w is double

(4)      第四步,如果前面的条件都不满足,则var的类型与expression的类型相同。

举例:

int j = 3;

int &k = j;

int &n = j;

decltype(j +6) i1; // i1 type int

decltype(100L) i2; // i2 type long

decltype(k+n) i3; //i3 type int

如果需要多次声明,可结合使用typedef 和decltype:

typedef decltype(x+y) xytype;

xytype xpy = x+y;

xytype arr[10];

xytype & rxy = arr[2]; //rxy a reference

 

16.  另一种函数声明语法(C++11后置返回类型)

对下面的原型:

double h(int x, float y);

使用新增语法可编写成:

auto h(int x, float y) -> double;

其中,auto是一个占位符,表示后置返回类型提供的类型,这是C++11给auto新增的一种角色。

新增这种语法的目的是为了解决以下出现的问题:

template <class T1, class T2>

?type? gt(T1 x, T2 y)

{

   …

   return x+y;

}

?type?是未知的类型,因为无法预知x和y相加得到的类型。原因是在确定该类型时,参数x和y尚未声明,同时也不在作用域内(编译器看不到它们,也无法使用它们),因此,必须在声明参数后使用decltype。需要注意:decltype(expression)中的expression出现的标识符,必须在函数参数列表中先出现。

举例:

template <class T1, class T2>

auto gt(T1 x, T2 y) -> decltype(x+y)

{

   return x+y;

}

之所以可以这样写,是因为decltype在参数x和y的声明后面,因此x和y位于作用域内,可以使用它们。

 

17.  程序常分为三部分:

(1)      头文件:包含结构声明和使用这些结构的函数的原型。(函数的声明)

(2)      源代码文件:包含与结构有关的函数的代码。(函数的定义,该文件可单独编译)

(3)      源代码文件:包含调用与结构有关的函数的代码。(函数的调用)

18.  头文件常包含的内容:

(1)      函数的原型。

(2)      使用#define或const定义的符号常量。

(3)      结构声明。(只是在源代码文件中声明结构变量时,告诉编译器如何创建该结构变量。)

(4)      类声明。

(5)      模板声明。(告诉编译器如何生成与源代码中的函数调用相匹配的函数定义)

(6)      内敛函数。

19.  通常,C++编译器既是编译程序,也管理链接器。编译器创建每一个源代码文件的目标代码文件;链接器将目标代码文件、库文件和启动代码文件合并,生成可执行文件。

20.  编译器要求,在同一个文件中只能将同一个头文件包含一次。为了避免多次包含同一个头文件,C/C++标准提供了一种技术,预处理器编译指令#ifndef(if not define)。

下面的代码片段意味着以前没有使用预处理器编译指令#define定义名称COORDIN_H_时,才处理#ifndef和#endif之间的语句:

#ifndef  COORDIN_H_

#endif

使用下面的语句就能完成对名称的定义:(名称定义常用于实现“开关”功能)

#define  COORDIN_H_

使用下面的语句完成创建符号常量:

#define  MAXIMUM  4096

21.  C++11使用四种不同的方案来存储数据,这些方案的区别在于数据保留在内存中的时间:

(1)      自动存储持续性。

在代码块中和在代码块中使用关键字register,这两类都属于自动存储持续性。通过栈来实现。

在C++11之前,自动存储使用关键字auto,寄存器变量用关键字register,但在C++11中,auto关键字用于自动类型推断,register关键字用于显示的自动的变量。关键字register使用它的唯一原因是,指出程序员想使用一个自动变量,这个变量的名称可能与外部变量相同。

(2)      静态存储持续性。

在函数定义外定义的变量和使用关键字static定义的变量,这两种的存储持续性都为静态的。它们在程序整个运行过程中都存在。

如果在函数定义外定义的变量使用了关键字static,则该变量具有内部链接性的静态变量。(关键字static用于改变变量的链接性,从外部链接性转化为内部链接性)如果没有显示的初始化静态变量,编译器将把它设置为0。

(3)      线程存储持续性。

如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。

(4)      动态存储持续性。

用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或者程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store)或堆(heap)。

22.  作用域和链接性

作用域:描述了名称在文件(翻译单元)的多大范围内可见。

                  作用域分两种:

                  (1)函数原型作用域。

(2)代码块(局部)。代码块是有花括号括起来的一系列语句,比如函数体就是代码块。

                  (3)文件(全局):从定义位置到文件结尾之间都可用。

a.自动变量的作用域是局部的,而静态变量的作用域是局部还是全局依照它的如何被定义的,比如,在代码块中定义的静态变量属于局部作用域,而不在任何函数内定义的变量属于全局作用域。

b. 在类中声明的成员的作用域属于整个类。

c. 在名称空间中声明的变量的作用域属于整个名称空间。

d. C++函数的作用域必须是整个类或者整个命名空间(包括全局的),但不能是局部的。

 

链接性:描述了名称如何在不同单元间共享。

C++提供三种链接性:

(1)      外部链接性。(可在其他文件中访问)

链接性为外部的变量通常称为外部变量,也称为全局变量(相对于局部的自动变量)。

(2)      内部链接性。(只能在当前文件中访问)

(3)      无链接性。(只能在当前函数或代码块中访问)

23.  关键字static

有两种用法:

(1)      改变存储持续性。发生在代码块中:从自动存储持续性转变为静态存储持续性。

(2)      改变链接性。发生在函数定义之外:从外部链接性转化为内部链接性。

24.  初始化

初始化分为两种:

(1)      静态初始化。在编译时进行初始化。

比如,以下为静态初始化:

#include <cmath>

int x; //zero – initialization 静态变量默认0作为初始化

int y = 5; //constant – expression initialization  常量表达式初始化

long z = 13*14; //constant –expression initialization

(2)      动态初始化。在编译后进行初始化。

const double pi = 4.0 * atan(1.0); //dynamicinitialization 动态初始化

25.  C++提供两种变量声明:

(1)      定义声明。它给变量分配存储空间。

(2)      引用声明。它不给变量分配存储空间,因为它引用已有的变量。引用声明使用关键字extern,且不进行初始化。

比如:

double up; //定义变量up,默认初始化为0

extern int blem; //变量blem的定义在别处,这里只是引用性声明。

extern double piu = 1.0; //因为有初始化,该语句属于定义性声明,该语句合法,此时,extern可有可无,但定义性声明只能出现一次,而引用性声明可以多次出现在其他文件中。(必须符合C++要求的“单定义规则”,也即,变量只能有一次定义)

注意:如果外部变量名和局部变量名重名了,外部变量被隐藏,如果需要访问外部变量,通过使用作用域解析运算符访问隐藏的外部变量。比如,cout<<::warmming;

26.  const char * const months[12] ={“January”, “February”, “March”, “April”, “May”, “June”, “July”, “August”,“September”, “October”, “November”, “December”};

对上述进行解释:定义了一个数组,数组中的每一个元素的类型为char * const的,也就是说,该指针的指向经首次初始化之后,不允许改变;而第一个const的作用是,说明,不允许通过该指针修改所指向的内容(数据)。

27.  共享数据

共享数据的方法:

(1)      使用全局变量(具有外部链接性),多个文件共享。

(2)      使用名称空间(默认情况下,链接性为外部的,除非它引用了常量)

28.  volatile限定符

在某些内存单元中,既是程序代码没有对该内存单元进行修改,其值也可能发生变化。而这种现象的出现对编译器优化是不利,比如,编译器发现程序在几条语句中使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中,这种优化假设变量的值在这两次使用之间不会变化。将变量声明为volatile,相当于告诉编译器不要做这种优化,因为该变量是不稳定的,需要到该变量的内存单元中去取数据。

29.  mutable

mutable指出,既是结构或类变量为const,声明为mutable的成员也可以被修改。

比如:

struct data

{

  char name[30];

   mutable intaccesses;

   …

};

const data veep = {“good”,0,…}; //定义const类型的veep变量

strcpy(veep.name, “bad”); //因为veep是const类型的变量,不允许修改

veep.accesses = 100;//虽然veep是const类型,但accesses声明为mutable,所以,可以修改。

30.  关键字const

(1)      const类型的内存被初始化后,程序便不能对它进行修改。

(2)      const限定符可改变变量的存储类型。这个特点跟static中的一个特性一样。比如,在默认情况下,全局变量的链接性为外部的,但const全局变量的链接性为内部的。(原因:正因为是内部链接性,const常量定义可以放在头文件,可被多个文件包含,这就意味着,每个文件都有自己的一组常量,而不是所有文件共享一组常量。假设是外部链接性,将出现多个定义,违反了“单定义规则”)

(3)      如果出于某种原因,程序员希望某个常量的链接性为外部的,则可以使用extern关键字来覆盖默认的内部链接性:

extern const int states = 50;//定义具有外部链接性的常量。

上面的extern关键字,是用来改变常量的链接性。这样的初始化定义只能出现一次,必须遵守“单定义规则”,在其他文件中,想使用该常量,必须使用extern关键字进行引用性声明。所以,上句不宜放在头文件中。

31.  函数和链接性

(1)      默认情况下,函数是静态的、全局的和外部链接性。想改变链接性通过static关键字,转化为内部链接性。

(2)      单定义规则也适用于非内联函数,对于每个非内联函数,程序只能有一个定义。对于链接性为外部的函数,在多文件程序中,只能有一个文件(该文件可能是库文件或是自己提供的文件)包含该函数的定义,但使用该函数的每个文件都应包含其函数原型。

(3)      如果文件中的函数原型指出该函数是静态的,则调用函数前,编译器将只在该文件中查找函数定义。

32.  C++语言链接性

链接程序要求每个不同的函数都有不同的符号名。在C++中由于同一个名称可能对应多个函数,则编译器必须将这些函数翻译成不同的符号名称,于是,C++编译器执行名称矫正或名称修饰,为重载函数生成不同的符号名称。

33.  5中存储方案和动态分配

C++为变量提供5中存储方案:代码区的自动变量、代码区的使用register关键字、代码区的使用static关键字、函数外的静态变量、函数外带static关键字的静态变量。

动态分配:使用C++运算符new(或C函数malloc()分配的内存),这种内存被称为动态内存。动态内存由运算符new和delete控制,不受作用域和链接性规则控制。

通常,编译器使用三块独立的内存:一块用于静态内存(可能再细分),一块用于自动变量,另一块用于动态存储。

34.  new 失败时,十年前的操作是返回空指针,但现在将引发异常std::bad_alloc

35.  可替换函数

使用new、new []和delete、delete []运算符会调用如下函数:
void * operator new(std::size_t); //used by new, 比如 new int 调用new(sizeof(int))

void * operator new[](std::size_t);

//used by new[], 比如 new int [4] 调用new(4*sizeof(int))

void * operator delete(void *);  //比如,delete pa 调用 delete(pa)

void * operator delete[](void *);

C++将上述这些函数称为可替换的。这意味着如果你有足够多的知识和意愿,可为new和delete提供替换函数,并根据需要进行定制。例如,可定义作用域为类的替换函数,并对其进行定制,以便满足该类的内存分配需求。在代码中,你仍将使用new运算符,但它调用您定义的new()函数。

36.  定位new运算符

通常,new负责在堆(heap)中找到一个足以能够满足要求的内存块。

而定位new运算符,它让你能够指定要使用的位置。

比如,char buffer[30];  int*p = new(buffer) int [20]; //使用buffer数组中的内存来创建。

定位new运算符不能使用delete运算符删除,因为delete只能用于这样的指针:指向常规new运算符分配的堆内存。

int *p = new(buffer)int [40]; //invokes new(40*siezeof(int), buffer);

定位new运算符不可替换,但可重载,它至少接受两个参数,其中第一个总是std::size_t,指定请求的字节数。

定位new运算符的一种用法:将其与初始化结合使用,从而将信息放在特定的硬件地址处。

37.  名称空间

1.      通过定义一种新的声明区域来创建命名的名称空间,这样做的目的之一是提供一个声明名称的区域。

2.      名称空间是开放的,可以把名称加入到已有的名称空间中。比如:

namespace Jill{char* goose(const char *);}  //将名称goose加入已有的Jill中

3.      除了用户定义的名称空间外,还存在另一个名称空间,就是全局名称空间(global namespace),它对应于文件级声明区域。全局变量就是位于全局名称空间中。

4.      using声明将特定的名称添加到它所属的声明区域中。例如,main()中使用using Jill::fetch,将名称fetch添加到main()定义的声明区域中。using声明使特定的标识符可用。

5.      using编译使整个名称空间可用,比如using namespace jack;using namespace std;

6.      如果试图使用using声明将名称空间中的名称导入到该声明区域,如果发生名称冲突,则会引发错误。如果使用using编译指令将该名称空间导入该声明区域,如果有相同的名称,则局部版本将隐藏名称空间版本。

7.      用户定义的名称空间,通过使用using声明或using编译允许程序的其他文件使用该名称空间中声明的东西。

8.      通常,将名称空间中的变量和函数的声明放在头文件中,而它们的定义放在源文件中。在其他文件中使用这些名称前,必须包含该头文件,并且使用using声明或using编译,之后才能使用该名称。可以这样理解:原本变量和函数的声明和定义都可以同时放在名称空间且同一个文件中,因为,名称空间是开放的,所以,可将声明和定义分成两个文件。将名称空间中的名称的声明和定义分开,有一个好处:将包含名称空间的头文件#include到其他文件,不会出现重定义。记住:多份声明不会有问题,但多份定义会有问题。

比如:

#include"namesmyth.h"  //目的:下面出现的名称(myth)告诉编译器去这个头文件去找。

using namespace myth;  //目的:将myth中的名称扩展它们的作用域到全局名称空间(具体点就是本文件)中。

int main()

{

display(); //函数调用

}

9.      名称空间声明可以进行嵌套,比如,using namespace elements::fire;//elements和fire都是名称空间,它们是包含关系。

10.  名称空间可以使用using编译指令和using声明,using编译指令和using声明可以对已有的名称进行作用域扩展,扩展到新的名称空间中。这样,名称可以通过这两个名称空间使用作用域解析运算符::访问它

11.  可以给名称空间增加别名。

namespace my_very_favorite_things

{

……

}

namespace mvft = my_very_favorite_things;//mvft成为my_very_favorite_things的别名。

12.  未命名的名称空间

未命名的名称空间中的名称只能在本文件中使用,由于没有名字,所以不能使用using 编译指令和using声明;它提供了链接性为内部的静态变量的替代品。比如:

static int counts;

int other();

int main()

{

 ……

}

int other()

{

……

}

等价于

namespace

{

int counts; //静态存储,内部链接性

}

int other();

int main()

{

 ……

}

int other()

{

……

}

38.  名称空间及其前途

(1)      尽量使用在已命名的名称空间中声明的变量,而不是使用外部全局变量和静态全局变量。

(2)      如果开发了一个函数库或类库,应将其放入名称空间中去。事实上,C++提倡将标准库函数放在名称空间std中。

(3)      导入名称时,首选使用作用域解析运算符或using声明的方法,尽量少用using编译指令。

(4)      对于using声明,首选将其作用域设置为局部而不是全局。

 



39.  指针需要的内存数量很可能与int相同,甚至在内部被表示为整数,但不能执行与整数相同的运算,这就是说,类型的不同决定运算操作的不同。总之,基本类型必须完成三项工作:

(1)      决定数据对象需要的内存数量。

(2)      决定如何解析内存中的位。(long和float在内存中占用的位数相同,但将他们转换为数值的方法不同)

(3)      决定可使用数据对象执行的操作和方法。

40.  什么是接口

接口是一个共享框架,供两个系统(如在计算机和打印机之间或者用户和计算机程序之间)交互时使用。

41.  结构的默认访问为public,类的默认访问为private。

42.  定义在类声明中的函数都将自动成为内联函数,类声明中常将短小的成员函数作为内敛函数。也可以在类声明中声明内敛函数(此时不需要inline),在类声明之外定义内敛成员函数(必须使用限定符inline)。关键字inline必须在内敛函数定义处添加。内敛函数的定义常放在类声明的头文件中,因为,要确保内敛定义在多个文件程序都有其定义的副本。

43.  在OOP中,调用成员函数被称为发送消息,因此将同样的消息发送给两个不同的对象将调用同一个方法,但该方法被用于两个不同的对象。

44.  对象初始化

1.      C++提供了两种使用构造函数来初始化对象的方式。

第一种方式是显示地调用构造函数。

第二种方式是隐式地调用构造函数。

Stock garment =Stock(“Furry Mason”,50,2.5); //显示地调用构造函数

等价于

Stock garment(“FurryMason”,50,2.5); //隐式地调用构造函数

每次创建类对象(包括用new运算符分配内存)时,C++都使用类构造函数。类构造函数是被用来构造对象,而不能通过对象来调用。

上述两种方式对对象初始化可以共同使用一种语法:都使用隐式调用构造函数这种语法。

这两种方式也可以使用不同的语法:

(1)      显示调用构造函数可以创建一个临时对象,然后将临时对象复制到garment中,并丢弃临时对象。如果编译器使用的是这种方式,则将为临时对象调用析构函数。

(2)      采用隐式调用构造函数的语法,不创建临时对象。

用构造函数来初始化数组元素:

Stock stocks [2]=

{

   Stock(“Furry Mason”,50,2.5),

   Stock(“NanoSmart”,12,4 ,20)

};

注意:初始化对象数组的方案是,首先使用默认构造函数创建数组的每一个元素,然后花挂号中的构造函数将创建临时对象,然后将临时对象的内容复制到数组对于的元素中。因此,要创建类对象数组,则这个类必须有默认构造函数。

2.      C++11提供了列表初始化

比如:

Stock hot_tip = {“Derivatives Plus Plus”, 100, 45.0}; //列表初始化对象hot_tip

Stock jock {“Sport Age Storage, Inc”};

Stock temp {};

45.  什么时候调用析构函数?

(1)      析构函数很少在代码中显示调用。比如在涉及定位new运算符时,需要显示调用析构函数。

(2)      析构函数通常是隐式自动调用的。如果创建的是静态存储类对象,则其析构函数将在程序结束时自动被调用。如果创建的是自动存储类对象,则其析构函数将在程序执行完代码块时自动调用。如果对象是通过new创建的,则它将驻留在栈内存或自由存储区中,当使用delete来释放内存时,其析构函数将被自动调用。

46.  对象赋给另一个对象

在默认情况下,将一个对象赋给另一个对象时,C++将源对象的每一个数据成员的内容复制到目标对象中相应的数据成员中。

47.  局部对象过期时,自动调用析构函数的顺序是最后被创建的对象将最先删除,最先创建的对象将被最后删除。这是因为自动变量是存储于栈中的。

48.  构造函数的用途

(1)      用于创建对象并初始化。可能会创建临时对象,也可能不会。详情请看第44点(C++提供了….)

(2)      创建临时对象,给对象赋值或返回临时对象

Stock stock1;

stock1 = Stock(“Nifty Foods”,10, 50.0); //用构造函数对对象赋值,总会在赋值前创建一个临时对象。

提示:初始化的效率比赋值的效率要更高。

函数结束可以出现:

return Vector(x + b.x , y + b.y); //创建一个临时无名对象,作为返回值。

(3)      可充当“转换函数”。只当参数数量只有一个时,才能实现参数类型转换到对象所属类型。

Stonewt(double lbs); //可将double类型的值转换为Stonewt类型。

Stonewt myCat = 19.7;

49.  为了保证成员函数不会去修改调用对象(*this),需要在语法上将调用对象设置为const类型。因此,在函数的挂号后面添加const关键字。比如:

void display() const ; // display属于const成员函数,该函数不会修改调用它的对象的值。此时,this指针限定为const,这样将不能使用this指针修改对象值。

在程序中,我们尽量使用const成员函数,尽量使用const引用和指针用作函数形参。

50.  在类中创建作用域为类的常量的方法

(1)      在有些编译器中可行有些不可行:

class Bakery

{

   private:

     const int Months =12;  //在类声明中创建一个常量。

     double costs[Months];

}

(2)      在类中定义常量方式,使用关键字static

(3)      在类中声明一个枚举。

 

51.  粗略理解:“对象保存数据,类提供方法”。

 

 

做补充总结:

1.      当基类和派生类都采用动态内存分配时,派生类的析构函数、构造函数、复制构造函数和赋值运算符都必须使用基类的方法来处理基类的元素。

对于析构函数,是自动完成的,也即,调用派生类的析构函数,会自动调用基类的析构函数。

对于构造函数,是可选的,必须在派生类构造函数初始化列表方式显示或隐式地调用基类的构造函数。

对于复制构造函数,是必须在初始化列表显示调用,必须在复制构造函数初始化列表中显示调用基类的复制构造函数。

对于赋值运算符函数,是必须在函数体内显示调用。

2.      构造函数和复制构造函数可以有多个,但析构函数只有一个,赋值运算符可能有两个(存在带const的和不带const的)。

 

 


1.      实现has-a关系,可以使用包含或者私有继承;使用包含的优点:易于理解、简单、包含能够包括多个同类的子对象;使用私有继承的优点:它提供的特性确实比包含的多,因为是继承,在派生类中不仅可以访问公有成员,而且还可以访问保护成员,而使用包含则不能访问保护成员,使用私有继承的情况还需要重新定义虚函数。

使用using 声明可以重新定义访问权限,using声明只适用于继承中,不适用于包含。

总之,应使用包含来建立has-a 关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承。

2.      在继承中,using 声明既可以用于重新定义访问权限,也可以实现函数的重载。

3.      在模板声明或模板函数定义内可以使用缩写式的类型,而在类外,必须使用完整的的类型。比如,

Stack & operator= (constStack & st); //此为在类模板函数声明中的写法。采用的是缩写式。

template <class Type> Stack<Type> &Stack<Type>::operator=(const Stack<Type> &st);

//此为在类外对函数进行定义时的函数头,必须采用完整的类型Stack<Type>

4.      模板的多功能性表现如下:

1)  模板类可用作基类

2)  模板类可用作组件类

3)  模板类可用作其他类模板的类型参数

4)  允许递归地使用模板,比如,ArrayTP<ArrayTP<int, 5> , 10> twodee;

//等价于int twodee[10][5];

       template <class T, int n> classArrayTP { }; //ArrayTP模板声明。其中,class T表明接受一个通用性的类型参数,T代替该类型。称为“常规参数”,

5)  模板允许使用多个类型参数

6)  模板类中的类型参数可以提供默认值,但函数模板中的参数类型不能提供默认值。

7)  模板还可以用于结构、模板成员

8)  模板可用作模板类的参数,这种称为“模板参数”。比如,

template< template <typename T> class Thing> class Crab { }; //其中Thing代表的是跟下划线这种声明相同的类模板的类型。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值