C++程序设计语言Note2

十四、命名空间

1.  组合问题(当你的程序进行模块化设计时,不同模块之间可能有同名的变量或函数,会产生冲突;)
2.  名字空间(@1 显式限定:可以在一个namespace内声明,然后用A::a在外面定义,但不可以直接采用A::a的方式定义;@2 using声明:可以用using A::a的方式声明在该声明同一个作用域内的直接对a的使用都是来自于名字空间A;@3 using指示:如using namespace std;表示以下使用的函数和变量不加限定时可以从该命名空间查找;@4 参数依赖查找:若在函数形参的类型声明时限定了如func(A::type a)的形式,则在func中有关于a的表达式中的变量和函数的查找范围都将包括A;当一个类成员调用一个命名函数时,编译器会优先查找同一个类其他成员或者其基类而不是基于参数类型查找到的函数;关联名字空间;@5 名字空间是开放的:命名空间内的函数声明可以是分离的,即可以在代码的不同位置声明同一个命名空间的不同函数或变量;)
3.  模块化和接口(接口和实现分离,相互只依赖于接口;@1 名字空间作为模块:可以用namespace分离不同的接口形成模块,如计算器例子中语法分析器接口都定义在一个namespace中,driver接口都声明在一个namespace中;@2 实现:不采用using、采用using声明和采用using指示对于模块化实现的影响;@3 接口和名字:在大规模程序中,建议将用户接口和实现接口进行分离;)
4.  组合使用名字空间(@1 便利性和安全性:using声明会将变量名加到作用域中,而using指示不会,也就是using声明产生命名冲突的概率更大;@2 名字空间别名:可以使用namespace ATT=American_Telephone_and_Telegragh的方式为长命名空间名字起一个别名;@3 组合名字空间:可以通过using指示用一个新的namespace将已有的namespace中的接口包含,这样可以在新定义的namespace中返回原有的旧的namespace中的接口,但不能定义;@4 组合与选择:当采用组合时,有可能有原有的两个命名空间内有同名变量或同名函数,而using指示可能会带来命名冲突,此时可以用using声明来指定默认的该函数是来自于原来的哪个命名空间;也可以用using表达式为原有的命名函数或变量起一个新名字;@5 名字空间和重载:重载是跨命名空间的,也就是说当重载的两个同名函数在不同的命名空间时,但你显式地用using指示包含了这两个命名空间,那么函数重载解析正常工作,不会产生名字冲突;@6 版本控制:随着语言的更新,可能不同的版本接口也不同,C++提供用一个大的namespace包含几个子命名空间并且将默认的版本声明为inline namespace的做法以提供版本控制;@7 名字空间嵌套:不建议这么做;@8 无名名字空间:有时将一组变量放在一个namespace中只是为了防止命名冲突而不是为了提供接口,此时可以用无名名字空间,无名名字空间都隐式地采用了using指示如using namespace $$$;@9 C头文件:可以将C语言头文件中的函数声明放在一个namespace中,这样就可以通过using指示该namespace提供向后兼容的能力;)

十五、源文件与程序

1.  分离编译(可以将一个程序按照逻辑功能分为多个源文件,编译器做的是编译单元,链接器通过静态链接或者动态链接的方式将多个源文件组合起来形成一个程序;)
2.  链接(非局部的各种变量或者函数名必须跨编译单元声明和定义一致,static、const、constexpr等都是内部链接,其中static要转换为外部链接只能先去掉static,而const可以直接声明extern指示其具有外部链接,局部变量是无链接的;inline函数在应用的所有编译单元内都必须有完全相同的定义,因此不允许在不同的源文件中定义名字相同的inline函数;@1 文件内名字:当想让该全局变量具有内部链接特性时,可以采用匿名名字空间和static声明两种方法;@2 头文件:#include<>和#include""区别,include头文件中应该包含的内容和不应包含的内容,include<>的<>中的空格不会被忽略;@3 单一定义准则:每个给定类、模板和枚举都只能在程序中被定义一次,当满足3个条件时,一个类、模板或内联函数的两个定义可以被接受认为是相同唯一定义,即不违反ODR;@4 标准库头文件:#include<>,C语言标准库都有对应的C++版本<cX>;@5 链接非C++代码:用extern "C"和连接块extern “C”{ }来指定C风格链接方式;@6 链接和函数指针:在声明链接方式时,此链接方式会应用于涉及到的所有函数名、函数类型和变量名,即可以涉及到传入的函数指针;)
3.  使用头文件(@1 单头文件组织:将所有接口声明都放在同一个头文件中,然后变量声明都用extern关键字声明;实现时有不同的命名空间对应不同的实现的cpp文件,并且每一个cpp文件中都包含共用的接口声明.h文件,适用于较小型程序;@2 多头文件组织:将用户接口与实现接口分离,用户接口只包含调用时所用的接口,实现接口包含实现时需要互相调用时用到的接口,实现接口包含对应的用户接口;& 2.1计算器程序其他模块:当一个程序逻辑模块实现需要共享上下文的很多函数组成时,才需要专门的_impl.h文件,计算器例子实例;& 2.2头文件的使用:结合使用单头文件和多头文件组织;@3 文件包含:采用包含保护实现允许重复包含头文件,即使用#ifndef…预处理指令;)
4.  程序(@1 非局部变量初始化:一般来说尽量减少全局变量的使用,可以用返回一个函数内部初始化的static变量的引用的方式,但这种方式也不是线程安全的;@2 初始化和并发:单一编译单元内的静态分配对象的初始化顺序与它们的定义顺序是一致的;但当使用多线程时,每个线程都会执行运行时初始化,系统并不隐含地应用互斥机制防止数据竞争,为避免该问题,提供了一些常用做法;@3 程序终止:6种程序终止的方式,调用exit()会调用已构造的静态对象的析构函数,但若是abort()就不会执行析构函数,调用exit()意味着调用函数的局部变量及其调用者不会执行各自的析构函数;atexit()给了在执行exit()时执行代码的机会;quick_exit()不执行任何析构;)
第三部分 抽象机制

十六、类

2.  类基础(@1 成员函数:声明于类定义的函数称为成员函数,成员函数种不用显式引用对象即可使用成员的名字;@2 默认拷贝:即编译器为每个类默认定义拷贝初始化操作和拷贝赋值运算符;@3 访问控制:可以用class中的public、private和protected(protected用于继承时访问控制)来进行访问控制,public提供接口,而private封装保护数据,private成员只能被该类成员函数访问;@4 class和struct:类定义即类声明,struct默认成员是public的,class默认是private的,当一个类是“具有不变式的真正类型”,应该用class;public和private声明可以多段分开声明;@5 构造函数:构造函数与类同名,类中所有对象都通过构造函数初始化,对象初始化时可以用{ }运算符,构造函数重载规则与普通函数相同,构造函数可以采用默认初始化;@6 explicit构造函数:explicit指明初始化时不能用隐式类型转换初始化对象,必须采用显式初始化(也称直接初始化);且当在类外构造函数定义时前有explicit,则类内构造函数声明时则不允许有explicit,即内外只能有一个explicit;一般构造函数多写上explicit;@7 类内初始化器:类的数据成员可以用int d{dd}的方式进行类内初始化,相当于在每一个构造函数版本内都提供了Constructor(int dd):d(dd);@8 类内函数定义:在类内直接进行函数定义相当于给函数提供了inline修饰符,最好不要这样使用;@9 可变性:可以定义具名的常量对象或变量对象,一个名字既可以指向不可变值的对象(const变量),也可以指向可变值的对象;& 9.1常量成员函数:成员函数后const声明表示该函数不改变对象的数据成员,若有操作企图改变,则报错;& 9.2物理常量性和逻辑常量性:有时一个成员函数逻辑上是const,但它仍然需要改变成员的值,这种称为逻辑常量性;& 9.3mutable:可以将一个类成员定义为mutable,表示即使在const对象中,也可以修改此成员;& 9.4间接访问实现可变性:当数据量很大时,采用mutable不很方便,可以将数据成员放入到另一个struct内,间接地访问它们;@10 自引用:对于一系列的不返回值的关联的成员函数,若想如.func1().func2().func3()这样串接使用,则可以声明其返回值是类本身的引用,而函数体返回值是*this,这样就可以实现上述串接操作;this的使用大部分是隐式的,但有时如链表时可能会使用显式this;@11 成员访问:使用.(点)运算符或使用->(箭头)运算符访问对象成员,当要访问一个类的成员而不是对象成员时,使用类名+::作用域运算符;@12 static成员:类的static成员对于类的所有实例都共用一个对象,即static成员只有一个副本,在类外定义stattic成员时,不需要再写上static声明符(只需要类内声明写);static成员可以用来做默认初始化的操作;@13 成员类型:类型和类型别名也可以作为类的成员,成员类(或称嵌套类)可以引用其所属类的类型和static成员,当给定所属类的一个对象时,只能引用所属类的非static成员;嵌套类可以访问其所属类的成员,相反一个类并没有任何特殊权限能访问其嵌套类的成员;)
3.  具体类(如果一个类的表示是其定义的一部分,则我们称它是具体的,以区别于抽象类和类层次中的类;@1 成员函数:具体类的每个成员函数都必须被实现;构造函数末尾可以应用于一个辅助函数检查初始化是否合法;@2 辅助函数:一个类往往有一些无须定义在类内的关联函数,因为它们不需要直接访问类的表示,可以用和类定义放在一个头文件中(早期做法),或者和类定义同一个命名空间等;@3 重载运算符:可以用重载运算符提供类的操作,赋值和拷贝初始化是默认提供的;@4 具体类的特性:具体类又称为值类型,可以被派生,但无法多态,具体类有更小的时空开销;)

十七、构造、清理、拷贝和移动

1.  引言(拷贝和移动的区别,移动长应用于资源管理类中;构造、拷贝、移动赋值和析构都涉及类的生命周期和资源管理;)
2.  构造函数和析构函数(定义一个构造函数指出一个类的对象如何初始化,定义一个析构函数确定对象被销毁时的清理操作,C++的资源管理与之密切相关;@1 构造函数和不变式:与类同名的成员函数为构造函数,有参数,无返回值,任务是初始化类的一个对象;初始化操作必须建立一个不变式,即初始化必须满足的特性;初始化遇到异常时,必须确保资源的回收;@2 析构函数和资源:用于销毁对象和回收资源的函数,前面加补号,不接受参数值,一般为隐式调用;RAII:通过构造和析构函数管理资源的方式;@3 基类和成员析构函数:构造函数自顶向下地创建对象,而析构函数销毁顺序与其相反,有virtual的基类一般是最顶层的;@4 调用构造函数和析构函数:当对象退出作用域或被delete时,析构函数会被隐式调用;一般情况下析构和构造都是隐式调用,但用实现可变内存的vector等时,用放置式new来push_back,用显式调用析构函数来pop_back;阻止隐式调用析构函数的方法有声明为=delete或者private限定,后者还可以显式(间接)访问;@5 virtual析构函数:对于含有virtual成员函数的类,其析构函数也应该是virtual的,否则基类资源不会被释放;)
3.  类对象初始化(@1 不使用类构造函数初始化:可以用逐成员初始化、默认初始化和拷贝初始化的方式不用构造函数也可以初始化;@2 使用构造函数初始化:定义了接受参数的构造函数,则编译器不会自动产生默认构造函数,用通用初始化{ }来初始化对象是最好的选择;& 2.1用构造函数进行初始化:{ }初始化与( )初始化的不同;@3 默认构造函数:无参的构造函数是默认构造函数,如果接受参数的构造函数提供默认参数,也是默认构造函数;内置类型具有默认构造和拷贝函数,但对于内置类型的未初始化变量默认构造函数不会被调用;引用和const变量必须被初始化,一个包含这些成员的类不会被默认构造,除非这些成员有类内初始化器或者由程序员定义了一个默认构造函数来初始化它们;@4 初始化器列表构造函数:接受单一std::initializer_list参数的构造函数被称为初始化器列表构造函数,一个初始化器列表构造函数使用一个{ }列表作为其初始化值来构造对象;& 4.1initializer_list构造消除歧义:当有多个构造函数时也会发生重载解析,当选择构造函数时,默认构造函数和初始化器列表构造函数优先;当匹配时,默认构造函数优先于列表初始化器构造函数优先于普通构造函数;& 4.2使用initializer_list:可以接受一个initializer_list参数的函数作为一个序列来访问,即通过成员函数begin()、end()和size()访问;initializer_list的元素是不可变的,不要考虑修改其值;& 4.3直接和拷贝初始化:{ }也存在直接初始化和拷贝初始化的区别;容器的初始化器列表构造函数可以是explicit,也可以不是,初始化器的列表的元素类型的构造函数可以是explicit,也可以不是;)
4.  成员和基类初始化(@1 成员初始化:可以通过成员初始化器列表的方式给出成员的构造函数的参数,构造函数的初始化顺序不按成员初始化器列表的顺序,而是按成员在类中声明的顺序,析构与构造顺序相反;& 1.1成员赋值和初始化:初始化和成员赋值有一定区别,一半来说初始化比赋值更好;@2 基类初始化器:派生类的基类的初始化方式与非数据成员相同,即如果基类要求一个初始化器,则我么你必须在构造函数中提供基类的初始化器,若希望默认构造,可以显式指出;@3 委托构造函数:如果你希望两个重载版本的重载构造函数做一部分相同的工作,可以用重复代码,也可以将相同的部分放到一个init()函数中,或者用委托构造函数(或称转发构造函数);如X():X(42),即将默认构造函数转发给X(int)来完成一部分工作;@4 类内初始化器:可以在类声明中为非static数据成员指定初始化器,且只能用{ }和=,但当类内初始化器依赖于全局变量时则有些不安全;@5 static成员初始化:一个static类成员是静态分配的,而不是每个类对象的一部分,一般来说static成员声明充当类外定义的声明,但在少数简单的特殊情况下,如static变量同时是const的或者constexpr,则也可以在类定义内初始化;)
5.  拷贝和移动(有两种不同的传值做法,拷贝和移动;@1 拷贝:类对象的拷贝操作有拷贝构造函数和拷贝赋值运算符两种,默认拷贝函数是浅拷贝,只拷贝成员的指针;拷贝构造与拷贝赋值运算符的区别是在于是否需要处理已有内存;安全的拷贝可以通过swap实现,这样可以避免异常时发生新值和旧值共存的现象;& 1.1小心默认构造函数:注意拷贝时要拷贝所有变量成员和基类对象,遗忘可能使得某变量获得一不想要的默认值;& 1.2拷贝基类:从拷贝的角度看,基类就是成员,拷贝时必须拷贝基类,如果你定义自己的拷贝构造函数,最简单的技术是重复拷贝virtual基类;& 1.3拷贝的含义:拷贝应该具有等价性和独立性;默认拷贝提供的是浅拷贝,你可以用copy-on-write写前拷贝技术来使用浅拷贝的效率,但其并不是万能灵药;& 1.4切片:一个指向派生类的指针可能会隐式转换为一个基类指针,当你用指针拷贝时,可能只拷贝了基类的部分而没有拷贝派生类的成员,导致切片;可以采用禁止拷贝基类(用=delete)或者防止派生类指针转换为基类指针的方法(将基类声明为protected或者private基类);@2 移动:为允许用户避免拷贝的逻辑和性能问题,C++不仅支持拷贝,而且支持移动操作,我们可以定义移动构造函数和移动赋值运算符来移动而非拷贝它们的参数;用右值引用表示移动的匹配,用std::move将实参变为一个右值引用传入到移动构造或移动赋值运算符;@3 重载运算符:可以用重载运算符提供类的操作,赋值和拷贝初始化是默认提供的;@4 具体类的特性:具体类又称为值类型,可以被派生,但无法多态,具体类有更小的时空开销;)
6.  生成默认操作(默认情况下,编译器会生成一个默认构造函数、一个拷贝构造函数、一个拷贝赋值运算符、一个移动构造函数、一个移动赋值运算符和一个析构函数;如果程序员为该类声明了任意构造函数,拷贝、移动操作和析构函数,则对应的操作则不会被默认生成;@1 显式声明默认操作:可以用=default标识使用默认构造或其它函数;@2 默认操作:每个生成的操作的默认含义,像编译器生成它们所用的实现方法一样,就是对类的每个基类和非static数据成员应用此操作;默认的移动操作是一种能令默认析构函数和默认拷贝赋值函数正确执行的状态,C++不保证任意操作在移出对象上都能正确执行,如return一个有string和int组成的类,string可被移出,但int被拷贝;@3 使用默认操作:& 3.1默认构造函数:默认的构造函数对每个成员进行默认初始化;% 3.2保持不变式:在一个类中,往往数据成员有一些规则要遵守,即不变式;应该在构造函数中建立不变式,在拷贝或移动中保持不变式,析构中释放所有资源;& 3.3资源不变式:不变式很多关键、明显的应用是管理资源;& 3.4部分说明的不变式:依赖于不变式但又只是通过构造函数和析构函数部分表达不变式的例子也可能存在;@4 使用delete删除的函数:可以用=delete表示没有这个函数,如防止拷贝基类而产生切片操作;我们可以delete任何我们能声明的函数,如我们可以将一个特例化版本从函数模板众多可能的特例化版本中删除,另一种是删除不需要的类型转换,进一步的用途是限制在哪里分配类对象;)

十八、运算符重载

1.  引言(算符可以帮助程序员更方便快捷地操作类对象,C++规定{ }只能表示初始化器,且只能在赋值运算符右侧;)
2.  运算符函数(可重载运算符、不可重载运算符,另外operator"“表用户定义字面值,operator T()表示向类型T的转换;C++不允许增加新运算符,可以用函数调用进行补充其他操作;@1 二元和一元运算符:我们可以用接受一个参数的非static成员函数定义二元运算符,也可以用接受两个参数的非成员函数定义它。对于任意一种二元运算符@,aa@bb等同于aa.operator@bb或者operator@(aa,bb),如果两种都被定义了,则由编译器重载解析规则选择到底执行哪一种;同样的,一元运算符和一元后置运算符也类似;运算符&&、||和,在重载后其求值顺序限制消失;@2 运算符的预置含义:当你重载了+和=后,编译器并不会自动生成重载后的+=;当作用于类对象时,=、&和,运算符有预置的含义,如果不想用可以用=delete声明;@3 运算符和用户自定义类型:运算符函数应该是成员函数或者至少接受一个用户自定义类型的参数(除了new和delete),不允许定义只处理指针的运算符函数;如果一个重载运算符接受一个内置类型作为它的第一个运算对象,那么该函数不能是成员函数,如重载了+运算符,可以使用aa+2,但是不能使用2+aa;@4 传递对象:向重载运算符传递时,也只有值传递和引用传递两种,值传递适用于小对象,引用传递适用于大对象,若传入的对象不希望被改变,则加上const限定符;若运算符返回的是其参数对象中的一个,则该运算符能够并且通常通过引用的方式返回;@5 名字空间中的运算符:运算符要么是类的成员函数,要么定义在某个命名空间内;编译器查找解析重载运算符时的顺序是,若给x@y,则先在X范围内匹配,再到x@y的上下文匹配,再到X的命名空间内匹配,再到Y的命名空间内匹配;)
3.  复数类型(@1 成员和非成员运算符:通过在类内定义只修改第一个参数的值的运算符,可以最大限度地减少直接操作对象内容的函数,在类的外部定义那些通过参数计算新值的运算符,这些运算符可以使用某些之前定义在类内的运算符;@2 混合模式运算:为了执行2+z,即不同类型的元素相加(处理混合模式运算),需要为+定义不同的重载版本,这些版本可以定义在类外;@3 类型转换:为了用标量对complex变量赋值和初始化,我们需要进行从标量向complex的类型转换,做法是提供相应的构造函数版本;& 3.1运算对象的类型转换:要想实现同一个函数的不同参数组合,另一种思路是运用类型转换,如将double转换为complex的构造函数定义后,即可以定义一个唯一的==运算符;利用类型转换技术为函数提供一个最通用的版本,再辅以少数的几种必要变形,这样做可以解决由混合模式运算带来的组合爆炸问题;@4 字面值常量:C++有内置类型的字面值常量,如1.2是double的字面值常量,我们可以通过将构造函数声明成constexpr,我们也能得到非常类似的complex字面值常量;也可以更进一步,为complex类型引入用户自定义的字面值常量,我们可以将i定义为后缀,它的意思是虚部,用constexpr complex<double> operator”" i(long double d){return {0,d}; };@5 访问函数:为complex的成员real和imag提供访问函数,一般情况下为每一个成员设置单独的访问函数是不必要的,但是诸如complex这些是必要的;@6 辅助函数:在同一命名空间内重新定义的二元运算符都可算为辅助函数;)
4.  类型转换(实现类型转换:接受单参的构造函数和类型转换运算符;在任意一种实现中,类型转换都能是explicit的或者隐式的;@1 类型转换运算符:接受单参数的构造函数执行类型转换虽然便捷,但构造函数无法指定从用户自定义类型向内置类型的隐式转换、从新类向已定义类的类型转换;可以为源类型定义类型转换运算符,成员函数X::operator T()定义了由X向T的类型转换,其中T是一个类型名;目标类型已经在函数中体现,所以不可以在函数定义时在函数前加T类型的返回类型说明;通常情况下,避免定义该类型转换运算符,因为它会自己隐式转换,并且在定义了类型转换的构造函数时可能会发生二义性匹配;@2 explicit类型转换运算符:类型转换运算符可以应用在代码的任何地方,然而最优的选择是把类型转换运算符声明为explicit并且明确只有当直接初始化时才使用它,当然也可以使用等价的explicit构造函数;@3 二义性:如果类X定义了一个赋值运算符X::operator=(Z),且类型V就是类型Z或者存在由类型V到类型Z的唯一类型转换,那么用V的值给X的对象赋值是合法的,初始化的情况与之类似;某些情况下,目标类型的值需要通过多次重复使用构造函数或者类型转换运算符来构造,此时我们必须使用显式类型转换,同一个层级内的自定义隐式类型转换只有一个才是合法的,反之,如果目标的值可以通过多种不同的方式创建(即产生二义性),这样的代码就是非法的;)

十九、特殊运算符

2.  特殊运算符([ ]、( )、->、++、–、new、delete运算符与常用的+、-有所不同;@1 取下标:可以用operator[ ]函数为类对象的下标赋予某种新的含义,operator[ ]函数的第二个参数(下标)可以是任意类型的,因此,它常被用于定义vector、关联数组等类型,operator[ ]( )必须是非static成员函数;@2 函数调用:函数调用expression可以看成是一个二元运算,它的左侧运算对象是expression,右侧运算对象是expression-list,调用运算符( )可以像其他运算符一样被重载;函数调用运算符常用来实现一个函数对象,lambda表达式本质上是定义函数对象的一种方式,operator()()必须是非static成员函数;@3 解引用:解引用运算符->(也称为箭头运算符)可以定义成一个一元后置运算符,对象p到指针p.operator->()的转换与其所指的成员m无关,这正是operator->()是一元后置运算符的意义,然后我们并没有引入任何新的语法,所以->之后仍然需要一个成员的名字;重载->的主要目的是创建“智能指针”,即,行为与指针类似的对象,并且当用其访问对象时执行某些操作;运算符->必须是非static成员函数,尽管->和.(点运算符)十分相似,但是我们并不能重载点运算符;@4 递增和递减:可以用重载的->运算符的Ptr<X>来代替一般的X *指针定义,这个版本可以执行类型检查;Ptr& operator++( )和Ptr* operator++(int)分别定义了前置和后置的++运算符,在实际应用中,应该尽量少使用后置的++运算符;@5 分配和释放:可以为某个类定义不同于全局的new和delete运算符,成员函数operator new()和operator delete()是隐式的static成员,因此它们无法使用this指针,也不能修改对象的值,它们提供了一块可供构造函数初始化并由析构函数释放的存储空间;@6 用户自定义字面值常量:可以通过字面值常量运算符定义用户自定义字面值常量,这类运算符负责把带后缀的字面值常量映射到目标类型,字面值常量运算符的名字由operator""加上后缀组成;可在整型字面值常量、浮点型字面值常量、字符串字面值常量和字符字面值常量四种常量后面添加构成用户自定义字面值常量;模板字面值常量运算符将其参数作为模板参数包而非函数参数包,由此可以实现可变参数模板;)
3.  字符串类(短字符串优化;@1 必备操作:类string提供了构造函数、析构函数以及赋值操作,深拷贝的string具有值语义的特性;@2 访问字符:提供了一组使用[ ]下标符号的未执行任何检查的操作以及一个执行边界检查的at()操作;@3 类的表示:string的表示应该满足易于把C风格字符串转换成string、尽量减少对自由存储的使用以及向string末尾添加字符的操作足够高效三个目标;使用了一个匿名联合来支持短字符串优化;& 3.1补充函数:第一个补充函数负责将字符移动到新分配的内存去,第二个补充函数是为拷贝操作服务的,它负责将一个string的成员的副本提供给另一个string,第三个是对应的移动操作;@4 成员函数:string类的主要成员函数有构造、拷贝移动和赋值,还有+=等操作;@5 辅助函数:一些函数如输入输出流、对范围for循环的支持、比较及连接操作以及用_s帮助字符串字面值常量定义;@6 应用string;)
4.  友元(一条普通的成员函数声明语句在逻辑上包含相互独立的三层含义:该函数有权访问类的私有成员、该函数位于类的作用域中和我们必须用一个含有this指针的对象调用该函数,当声明为static时,该函数只有前两层含义,当声明为friend时,该函数只有第一层含义;友元应用的例子如matrix和vector相乘;可以令一个类或一个类的某成员函数是另一个类的友元;@1 发现友元:友元必须在类的外层作用域中提前声明,或者定义在直接外层非类作用域中;当友元接受参数的时候,可以不是声明在直接外层作用域中;@2 友元与成员:需要直接访问类的表示部分的函数应该定义为类的成员,优先考虑定义为成员函数,二元运算符考虑定义为友元;)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值