《21天学通C++》读书笔记,名字很奇怪,但写的还蛮好,

变量长度指的是:程序员声明变量时,编译器将预留多少内存,用于存储赋给该变量的数据。变量的长度随类型而异,C++提供了一个方便的运算符——sizeof,可用于确定变量的长度(单位为字节)或类型。

在有些情况下,根据赋给变量的初值,很容易知道其类型。例如,如果将变量的初值设置成了 true,就可推断其类型为bool。在C++11中,可不显式地指定变量的类型,而使用关键字auto

使用auto时必须对变量进行初始化,因为编译器需要根据初始值来确定变量的类型。如果将变量的类型声明为auto,却不对其进行初始化,将出现编译错误。


乍一看,auto 并非什么了不起的功能,但在变量类型非常复杂时,它可让编程容易得多。假设您使用std::vector声明了一个名为MyNumbers的动态整数数组:

要访问或遍历该数组中的元素并显示它们,可使用如下代码:

std::vector和for循环都还没有介绍,因此即便上述代码看起来像天书,也不用担心。其功能如下:对于矢量中的每个元素——从begin()开始,到end()前面的一个元素结束,都使用cout显示其值。第1行代码非常复杂,它声明变量Iterator,并将其初始值设置为begin()返回的值。这个变量的类型为
vector<int>::const_iterator,这对程序员来说,学习和书写起来都非常复杂。程序员无需熟记这一点,而可依赖于begin()的返回类型,将上述for循环简化为如下所示:

注意到现在第1行有多紧凑。编译器检查Iterator的初始值——begin()返回的值,并将该变量的类型设置为该返回值的类型。这简化了C++编码工作,在大量使用模板时尤其如此。

因此,在 C++中,常量类似于变量,只是不能修改。与变量一样,常量也占用内存空间,并使用名称标识为其预留的空间的地址,但不能覆盖该空间的内容。在C++中,常量可以是:
• 字面常量;

• 使用关键字const声明的常量;

• 使用关键字constexpr声明的常量表达式(C++11新增的);

• 使用关键字enum声明的枚举常量;

• 使用#define定义的常量(已摒弃,不推荐)。

在C++11之前,C++就支持常量表达式的概念,只是没有关键字constexpr。在程序清单3.5中, 22.0/7 是一个常量表达式,C++11 之前的编译器也支持它。然而,C++11 之前的编译器不允许定义在编译阶段计算的函数。在C++11中,可以编写下面这样的代码:


这样的数组被称为静态数组,因为在编译阶段,它们包含的元素数以及占用的内存量都是固定的

如果在第5行声明并初始化字符数组时忘记添加‘\0’,则打印该数组时,Hello World后面将出现垃圾字符,这是因为 std::cout只有遇到空字符后才会停止打印,即便这将跨越数组的边界。

逻辑OR运算用运算符|表示。

逻辑XOR(异或)运算与逻辑OR运算稍有不同,有且只有一个操作数为true时,这种运算的结果才为true,如表5.5所示。
C++提供了按位XOR运算,用运算符^表示。这个运算符对操作数相应的各位执行XOR运算。

sizeof(…)看起来像函数调用,但它并不是函数,而是运算符。有趣的是,程序员不能定义这个运算符,因此不能重载它。

在需要动态地给 N 个对象(尤其是您自己创建的类型)分配内存时,sizeof 很有用。您可以使用sizeof确定每个对象占用的内存量,再使用运算符new动态地分配内存。
动态内存分配将在第8章详细介绍。

条件运算符,也叫三目运算符
可使用这个运算符获得两个数字中较大的那个:

continue让您能够跳转到循环开头,跳过循环块中后面的代码。因此,在while、do…while和for循环中,continue导致重新评估循环条件,如果为true,则重新进入循环块。

理想情况下,while循环表达式应为布尔值true或false,否则这样解读:零表示false,非零表示true。由于-1不是零,因此该while条件为true,循环将执行。如果希望仅当Integer为正数时才执行循环,可编写表达式while(Integer>0)。这种规则适用于所有的条件语句和循环

带默认值参数的函数
请注意第二个参数Pi及其默认值3.14。对调用者来说,这个参数是可选的,因此仍可使用下面的语法来调用Area:
这里省略了第二个参数,因此它将使用默认值3.14。然而,如果用户提供了Pi值,您就可在调用Area时指定它,如下所示:

栈的性质使其非常适合用于处理函数调用。函数被调用时,所有局部变量都在栈中实例化,即被压入栈中。函数执行完毕时,这些局部变量都从栈中弹出,栈指针返回到原来的地方。

与大多数变量一样,除非对指针进行初始化,否则它包含的值将是随机的。您不希望访问随机的内存地址,因此将指针初始化为NULL。NULL是一个可以检查的值,且不会是内存地址:


要编写根据用户需要使用内存资源的应用程序,需要使用动态内存分配。这让您能够根据需要分配更多内存,并释放多余的内存。为帮助您更好地管理应用程序占用的内存,C++提供了两个运算符——new和delete。指针是包含内存地址的变量,在高效地动态分配内存方面扮演了重要角色。
您使用new来分配新的内存块。通常情况下,如果成功,new将返回指向一个指针,指向分配的内存,否则将引发异常。使用new时,需要指定要为哪种数据类型分配内存:
需要为多个元素分配内存时,还可指定要为多少个元素分配内存:
对于使用new[…]分配的内存块,需要使用delete[]来释放;对于使用new为单个元素分配的内存,需要使用delete来释放

不能将运算符delete用于任何包含地址的指针,而只能用于new返回的且未使用delete释放的指针。

第3章介绍过,通过将变量声明为const的,可确保变量的取值在整个生命周期内都固定为初始值。这种变量的值不能修改,因此不能将其用作左值。

指针也是变量,因此也可将关键字const用于指针。然而,指针是特殊的变量,包含内存地址,还可用于修改内存中的数据块。因此,const指针有如下三种。
• 指针指向的数据为常量,不能修改,但可以修改指针包含的地址,即指针可以指向其他地方。
• 指针包含的地址是常量,不能修改,但可修改指针指向的数据。
• 指针包含的地址以及它指向的值都是常量,不能修改(这种组合最严格)。
将指针传递给函数时,这些形式的const很有用。函数参数应声明为最严格的const指针,以确保函数不会修改指针指向的值。这让函数更容易维护,在时过境迁和人员更迭后尤其如此。

换句话说,数组类似于在固定内存范围内发挥作用的指针。可将数组赋给指针,如第11行所示,但不能将指针赋给数组,因为数组是静态的,不能用作左值。
除非请求分配的内存量特大,或系统处于临界状态,可供使用的内存很少,new 一般都能成功。有些应用程序需要请求分配大块的内存(如数据库应用程序),一般而言,不要假定内存分配能够成功,这很重要。C++提供了两种确保指针有效的方法,默认方法是使用异常,即如果内存分配失败,将引发std::bad_alloc异常。这导致应用程序中断执行,除非您提供了异常处理程序,否则应用程序将崩溃,并显示一条类似于“异常未处理”的消息。

除非请求分配的内存量特大,或系统处于临界状态,可供使用的内存很少,new 一般都能成功。有些应用程序需要请求分配大块的内存(如数据库应用程序),一般而言,不要假定内存分配能够成功,这很重要。 std::bad_alloc异常。这导致应用程序中断执行,除非您提供了异常处理程序,否则应用程序将崩溃,并显示一条类似于“异常未处理”的消息。
第 28 章将详细讨论如何解决这种问题。程序清单 8.15 演示了如何使用异常处理检查分配请求是否失败。
程序清单8.15 异常处理——在new失败时妥善地退出
有一个 new 变种—— new(nothrow),它不引发异常,而返回 NULL,让您能够在使用指针前检查其有效性,如程序清单8.16所示。
程序清单8.16 使用new(nothrow),它在分配内存失败时返回NULL

const关键字和引用
可能需要禁止通过引用修改它指向的变量的值,为此可在声明引用时使用关键字const:


delete摧毁的是地址,不是指针,如果指针之间相互指,也只需要摧毁掉一个指针(也就是地址)。


封装指的是将数据以及使用它们的方法进行逻辑编组,这是面向对象编程的重要特征。

C++中类实例化
就像为int动态分配内存一样,也可使用new为Human对象动态地分配内存:
小程序直接申明,大程序用new

构造函数是一种特殊的函数(方法),在创建对象时被调用。与函数一样,构造函数也可以重载。
构造函数是一种特殊的函数,它与类同名且不返回任何值。因此,Human类的构造函数的声明类似于下面这样:
这个构造函数可在类声明中实现,也可在类声明外实现。在类声明中实现(定义)构造函数的代码类似于下面这样:
在类声明外定义构造函数的代码类似于下面这样:

::被称为作用域解析运算符。例如,Human::DateOfBirth指的是在Human类中声明的变量DateOfBirth,而::DateOfBirth表示全局作用域中的变量DateOfBirth。

C++编译器不会为您生成默认构造函数。
默认构造函数是调用时可不提供参数的构造函数,而并不一定是不接受任何参数的构造函数。因此,下面的构造函数虽然有两个参数,但它们都有默认值,因此也是默认构造函数:
因为实例化Human对象时仍可不提供任何参数:

每当对象不再在作用域内或通过 delete 被删除,进而被销毁时,都将调用析构函数。这使得析构函数是重置变量以及释放动态分配的内存和其他资源的理想场所。

程序清单9.7所示的MyString类包含一个指针成员,它指向动态分配的内存(这些内存是在构造函数中使用new分配的,并在析构函数中使用delete[]进行释放)。复制这个类的对象时,将复制其指针成员,但不复制指针指向的缓冲区,其结果是,两个对象指向同一块动态分配的内存。这被称为浅复制,会威胁程序的稳定性,如程序清单9.8所示。
在这里,编译器没有进行深复制,因为编译时它不确定指针成员MyString::Buffer指向的是多少字节的内存。

复制构造函数是一个特殊的重载构造函数,编写类的程序员必须提供它。每当对象被复制(包括将对象按值传递给函数)时,编译器都将调用复制构造函数。

为MyString类声明复制构造函数的语法如下:

赋值构造函数传递的是引用参数。 http://blog.csdn.net/lwbeyond/article/details/6202256
CExample(const CExample& C) 就是我们自定义的拷贝构造函数。可见,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。

通过在复制构造函数声明中使用const,可确保复制构造函数不会修改指向的源对象。另外,复制构造函数的参数必须按引用传递,否则调用它时将复制实参的值,导致对源数据进行浅复制——这正是您要极力避免的。 http://blog.csdn.net/jofranks/article/details/17438955
在初始化表中,成员被初始化的次序不是你编写初始化表的次序,而是定义成员的次序。

要禁止类对象被复制,可声明一个私有的复制构造函数。这确保函数调用DoSomething(OurPresident)无法通过编译。为禁止赋值,可声明一个私有的赋值运算符。

由于复制构造函数是私有的,其中每个对象都是不可复制的,但您的目标是确保President类有且只有一个化身,即有了一个President对象后,就禁止创建其他的President对象。要实现这种功能强大的模式,可使用单例的概念,它使用私有构造函数、私有赋值运算符和静态实例成员。

这个程序清单表明,对于使用new在自由存储区中实例化的派生类对象,如果将其赋给基类指针,并通过该指针调用delete,将不会调用派生类的析构函数。这可能导致资源未释放、内存泄露等问题,必须引起重视。

如果对象是使用 new在自由存储区中实例化的,或者有指向对象的指针,则可使用指针运算符(->)来访问成员属性和方法:

引用作为函数返回值

通过使用const,可禁止从外部通过运算符[]直接修改成员MyString::Buffer。除将返回类型声明为const外,还将该运算符的函数类型设置成为const,这将禁止该运算符修改类的成员属性。一般而言,应尽可能使用const,以免无意间修改数据,并最大限度地保护类的成员属性。
实现下标运算符时,可在程序清单 12.10 所示版本的基础上进行改进。这个版本只实现了一个下标运算符,它可用于读写动态数组的元素。
然而,也可实现两个下标运算符,其中一个为const函数,另一个为非const函数:
编译器很聪明,能够在读取MyString对象时调用const函数,而在对MyString执行写入操作时调用非const函数。因此,如果愿意,可在两个下标函数中实现不同的功能。例如,一个运算符记录对容器的写入操作,而另一个记录对容器的读取操作。还有其他双目运算符可被重定义或重载(如表 12.2所示),但本章不打算介绍它们。这些运算符的实现与已讨论的运算符类似。
如果其他运算符(如逻辑运算符和按位运算符)有助于改善您编写的类,就应实现它们。显然,诸如Date等日历类没有必要实现逻辑运算符,但处理字符串和数字的类可能需要实现它们。
应根据类的目标和用途重载运算符或实现新的运算符。

operator()让对象像函数,被称为函数运算符。函数运算符用于标准模板库(STL)中,通常是 STL算法中。其用途包括决策。根据使用的操作数数量,这样的函数对象通常称为单目谓词或双目谓词。下面分析一个非简单的函数对象,如程序清单12.11所示,以便理解使用如此有意思的名称的原因!
程序清单12.11 一个使用operator()实现的函数对象

复制构造函数是一个特殊的重载构造函数,编写类的程序员必须提供它。每当对象被复制(包括将对象按值传递给函数)时,编译器都将调用复制构造函数为MyString类声明复制构造函数的语法如下:

程序清单9.7中,这个类运行正常,为何会导致程序清单9.8崩溃呢?相比于程序清单9.7,程序清单 9.8 唯一不同的地方在于,在 main()中,将使用 MyString 对象 SayHello 的工作交给了函数UseMyString,如第54行所示。在main()中将工作交给这个函数的结果是,对象SayHello被复制到形参Input,并在UseMyString()中使用它。编译器之所以进行复制,是因为函数SayHello的参数Input被声明为按值(而不是按引用)传递。对于整型、字符和原始指针等POD数据,编译器执行二进制复制,因此SayHello.Buffer包含的指针值被复制到Input中,即SayHello.Buffer和Input.Buffer指向同一个内存单元,如图9.3所示
二进制复制并不深复制指向的内存单元,这导致两个 MyString 对象指向同一个内存单元。函数UseMyString()返回时,变量Input不再在作用域内,因此被销毁。为此,将调用MyString类的析构函数,而该析构函数使用delete释放分配给Buffer的内存(如程序清单9.8的第26行所示)。这将导致main()中的对象 SayHello 指向的内存无效,而等 main()执行完毕时,SayHello 将不再在作用域内,进而被销毁。然而,第26行对不再有效的内存地址调用 delete(销毁Input时释放了该内存,导致它无效)。图9.2的调试断言消息指出错误出在第52行(本书的行号从零开始,因此是第51行),因为未能成功地销毁这里创建的对象SayHello。
在这里,编译器没有进行深复制,因为编译时它不确定指针成员MyString::Buffer指向的是多少字节的内存。

复制构造函数是一个特殊的重载构造函数,编写类的程序员必须提供它。每当对象被复制(包括将对象按值传递给函数)时,编译器都将调用复制构造函数

因此,要让 cout能够显示Date对象,只需添加一个返回 const char*的运算符:

猜你喜欢

转载自blog.csdn.net/sinat_14884161/article/details/50961092