《C++Primer》学习笔记3--:第二章(P42-P65)

来源:互联网 发布:come alive动作数据 编辑:程序博客网 时间:2024/05/16 11:37

《C++Primer》学习笔记3

——2013.12.16周一   第一周第7天 任务量:第二章(P50-P65)

l  C++ 支持两种初始化变量的形式:复制初始化直接初始化。复制初始化语法用等号(=),直接初始化则是把初始化式放在括号中:

int ival(1024);    // direct-initialization

int ival = 1024;   // copy-initialization

这两种情形中,ival 都被初始化为 1024

l  使用 = 来初始化变量使得许多C++ 编程新手感到迷惑,他们很容易把初始化当成是赋值的一种形式。但是在 C++初始化和赋值是两种不同的操作。这个概念特别容易误导人,因为在许多其他的语言中这两者的差别不过是枝节问题因而可以被忽略。即使在 C++ 中也只有在编写非常复杂的类时才会凸显这两者之间的区别。无论如何,这是一个关键的概念,也是我们将会在整本书中反复强调的概念。虽然在本书到目前为止还没有清楚说明,但是在 C++ 中理解“初始化不是赋值”是必要的。初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。

当初始化类类型对象时,复制初始化和直接初始化之间的差别是很微妙的。我们在第十三章再详细解释它们之间的差别。现在我们只需知道,直接初始化语法更灵活且效率更高。

对内置类型来说,复制初始化和直接初始化几乎没有差别。对类类型的对象来说,有些初始化仅能用直接初始化完成。

Using MultipleInitializers使用多个初始化式

例如string 类定义了几个构造函数,使得我们可以用不同的方式初始化 string 对象。其中一种初始化 string 对象的方式是作为字符串字面值的副本:

#include <string>
// alternative ways to initialize string from a character string literal
std::string titleA = "C++ Primer, 4th Ed.";
std::string titleB("C++ Primer, 4th Ed.");

本例中,两种初始化方式都可以使用。两种定义都创建了一个 string 对象,其初始值都是指定的字符串字面值的副本。

也可以通过一个计数器和一个字符初始化string对象。这样创建的对象包含重复多次的指定字符,重复次数由计数器指定:

std::string all_nines(10, '9');   // all_nines= "9999999999"

本例中,初始化 all_nines 的唯一方法是直接初始化。有多个初始化式时不能使用复制初始化。

l  内置类型变量是否自动初始化取决于变量定义的位置。在函数体外定义的变量都初始化成 0,在函数体里定义的内置类型变量不进行自动初始化。除了用作赋值操作符的左操作数,未初始化变量用作任何其他用途都是没有定义的。未初始化变量引起的错误难于发现。正如我们在第 2.2 节劝告的,永远不要依赖未定义行为。

问题出在未初始化的变量事实上都有一个值。编译器把该变量放到内存中的某个位置,而把这个位置的无论哪种位模式都当成是变量初始的状态。当被解释成整型值时,任何位模式都是合法的值——虽然这个值不可能是程序员想要的。因为这个值合法,所以使用它也不可能会导致程序崩溃。可能的结果是导致程序错误执行和/或错误计算。

l  变量的定义用于为变量分配存储空间,还可以为变量指定初始值。在一个程序中,变量有且仅有一个定义

声明用于向程序表明变量的类型和名字。定义也是声明:当定义变量时我们声明了它的类型和名字。可以通过使用extern关键字声明变量名而不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern:

      extern inti;   // declares but does not define i

      int i;          // declares and defines i

extern 声明不是定义,也不分配存储空间。事实上,它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。

只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。初始化式必须要有存储空间来进行初始化。如果声明有初始化式,那么它可被当作是定义,即使声明标记为 extern:extern double pi = 3.1416; // definition

只有当 extern 声明位于函数外部时,才可以含有初始化式。因为已初始化的 extern 声明被当作是定义,所以该变量任何随后的定义都是错误的:

      extern doublepi = 3.1416; // definition

      doublepi;                 // error:redefinition of pi

同样,随后的含有初始化式的 extern 声明也是错误的.

任何在多个文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。在 C++ 语言中,变量必须且仅能定义一次,而且在使用变量之前必须定义或声明变量。

l   

2.4 const 限定符提供了一个解决办法,它把一个对象转换成一个常量。      const int bufSize = 512;     // input buffer size

定义bufSize 为常量并初始化为 512。变量bufSize 仍然是一个左值(第 2.3.1 节),但是现在这个左值是不可修改的。任何修改bufSize 的尝试都会导致编译错误

因为常量在定义后就不能被修改,所以定义时必须初始化:

l  在全局作用域(第 2.3.6 节)里定义非 const 变量时,它在整个程序中都可以访问。我们可以把一个非const 变更定义在一个文件中,假设已经做了合适的声明,就可在另外的文件中使用这个变量:

     // file_1.cc
     int counter;  // definition
      // file_2.cc
      extern int counter; // uses counter from file_1
      ++counter;          // increments counter defined in file_1

l  与其他变量不同,除非特别说明,在全局作用域声明的 const 变量是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其他文件访问。

const 变量默认为extern。要使 const 变量能够在其他的文件中访问,必须地指定它为extern

// file_1.cc
      // defines and initializes a const that is accessible to other files
      extern const int bufSize = fcn();
      // file_2.cc
      extern const int bufSize; // uses bufSize from file_1
      // uses bufSize defined in file_1
      for (int index = 0; index != bufSize; ++index)
            // ...

本程序中,file_1.cc 通过函数 fcn 的返回值来定义和初始化 bufSize。而 bufSize 定义为extern,也就意味着bufSize 可以在其他的文件中使用。file_2.ccextern 的声明同样是extern;这种情况下,extern标志着bufSize 是一个声明,所以没有初始化式。

2.5. 引用引用是一种复合类型,通过在变量名前添加“&”符号来定义。复合类型是指用其他类型定义的类型。在引用的情况下,每一种引用类型都“关联到”某一其他类型。不能定义引用类型的引用,但可以定义任何其他类型的引用。

引用必须用与该引用同类型的对象初始化:

      int ival = 1024;
      int &refVal = ival; // ok: refVal refers to ival
      int &refVal2;       // error: a reference must be initialized
      int &refVal3 = 10;  // error: initializer must be an object

l  当引用初始化后,只要该引用存在,它就保持绑定到初始化时指向的对象。不可能将引用绑定到另一个对象。引用只是对象的另一名字。作用在引用上的所有操作事实上都是作用在该引用绑定的对象上:

const引用是指向const 对象的引用:

    const int ival = 1024;
    const int &refVal = ival; // ok: both reference and object are const
    int &ref2 = ival; // error: non const reference to a const object

非 const 引用只能绑定到与该引用同类型的对象。const 引用则可以绑定到不同但相关的类型的对象或绑定到右值。

int i = 42;
      //  legal for const references only
      const int &r = 42;
      const int &r2 = r + i;

同样的初始化对于非 const 引用却是不合法的,而且会导致编译时错误。观察将引用绑定到不同的类型时所发生的事情,最容易理解上述行为。假如我们编写

      double dval = 3.14;
      const int &ri = dval;

the compiler transforms this code intosomething like this:

编译器会把这些代码转换成如以下形式的编码:

      int temp = dval;          // create temporary int from the double
      const int &ri = temp;   // bind ri to that temporary

如果 ri 不是const,那么可以给 ri 赋一新值。这样做不会修改 dval,而是修改了temp。期望对ri 的赋值会修改 dval 的程序员会发现 dval 并没有被修改。仅允许const 引用绑定到需要临时使用的值完全避免了这个问题,因为const 引用是只读的。

l  2.7枚举的定义包括关键字 enum,其后是一个可选的枚举类型名,和一个用花括号括起来、用逗号分开的枚举成员列表。

     // input is 0, output is 1, and append is 2

    enum open_modes {input, output, append};

默认地,第一个枚举成员赋值为 0,后面的每个枚举成员赋的值比前面的大 1。

不能改变枚举成员的值。枚举成员本身就是一个常量表达式,所以也可用于需要常量表达式的任何地方。

l  用 classstruct 关键字定义类的唯一差别在于默认访问级别:默认情况下,struct 的成员为public,而class 的成员为 private

头文件用于声明而不是用于定义. 定义只可以出现一次,而声明则可以出现多次(第 2.3.5 节)。下列语句是一些定义,所以不应该放在头文件里:

     extern int ival = 10;      // initializer, so it's a definition
     double fica_rate;          // no extern, so it's a definition
同一个程序中有两个以上文件含有上述任一个定义都会导致多重定义链接错误。

对于头文件不应该含有定义这一规则,有三个例外。头文件可以定义类、值在编译时就已知道的const 对象和 inline 函数(第 7.6 节介绍inline 函数)。这些实体可在多个源文件中定义,只要每个源文件中的定义是相同的。

l  一般来说,常量表达式是编译器在编译时就能够计算出结果的表达式。当const 整型变量通过常量表达式自我初始化时,这个const 整型变量就可能是常量表达式。而const 变量要成为常量表达式,初始化式必须为编译器可见。为了能够让多个文件使用相同的常量值,const 变量和它的初始化式必须是每个文件都可见的。而要使初始化式可见,一般都把这样的const 变量定义在头文件中。那样的话,无论该const 变量何时使用,编译器都能够看见其初始化式。

但是,C++ 中的任何变量都只能定义一次(第 2.3.5 节)。定义会分配存储空间,而所有对该变量的使用都关联到同一存储空间。因为const 对象默认为定义它的文件的局部变量,所以把它们的定义放在头文件中是合法的。

这种行为有一个很重要的含义:当我们在头文件中定义了 const 变量后,每个包含该头文件的源文件都有了自己的 const 变量,其名称和值都一样。

当该 const 变量是用常量表达式初始化时,可以保证所有的变量都有相同的值。但是在实践中,大部分的编译器在编译时都会用相应的常量表达式替换这些const 变量的任何使用。所以,在实践中不会有任何存储空间用于存储用常量表达式初始化的const 变量。

如果 const 变量不是用常量表达式初始化,那么它就不应该在头文件中定义。相反,和其他的变量一样,该 const 变量应该在一个源文件中定义并初始化。应在头文件中为它添加extern 声明,以使其能被多个文件共享。

const double pi = 3.1416;是用常量表达式初始化的,应该放在头文件中
const double sq2 = sqrt(2.0);不是用常量表达式初始化的,应放在源文件中

l  避免多重包含   为了避免名字冲突,预处理器变量经常用全大写字母表示。

预处理器变量有两种状态:已定义或未定义。定义预处理器变量和检测其状态所用的预处理器指示不同。#define 指示接受一个名字并定义该名字为预处理器变量。#ifndef 指示检测指定的预处理器变量是否未定义。如果预处理器变量未定义,那么跟在其后的所有指示都被处理,直到出现#endif

可以使用这些设施来预防多次包含同一头文件:

     #ifndef SALESITEM_H
     #define SALESITEM_H
     // Definition of Sales_itemclass and related functions goes here
     #endif

 

0 0