当前位置: 首页 > news >正文

c++primer 个人学习总结--高级主题

        只学了一部分。。。


        tuple类型

        tuple是类似pair的模板。每个pair的成员类型都不相同,但每个 pair 都恰好有两个成员。不同 tuple类型的成员类型也不相同,但一个tuple可以有任意数量的成员。每个确定的 tuple 类型的成员数目是固定的,但一个tuple类型的成员数目可以与另一个tuple类型不同。当我们希望将一些数据组合成单一对象, 但又不想麻烦地定义一个新数据结构来表示这些数据时,tuple 是非常有用的。

        定义和初始化tuple:当我们创建一个tuple对象tuple<string,int> t时,可以用tuple的默认构造函数,它会对tuple的每个成员进行值初始化。tuple可以用列表初始化,也可以用直接构造初始化。他们都只能一个参数对应一个元素的构造函数。

        访问tuple成员:访问一个tuple成员需要使用名为get的标准库函数模板。差不多就像这样,auto sth=get<3>(a_tuple); 尖括号的值必须是一个整型常量表达式,因为模板实例化必须在编译器就生成对应的函数版本,而auto也必须在编译器就能推导出来对应的元素的类型。

        关系和相等运算符:只有元素数量相等的tuple才可以进行比较。而且,对每对成员调用对应的关系运算符时都必须合法才行。两个tuple相等要求元素数量相等,对应成员相等。

        注:使用tuple可以方便快捷的让函数返回多个值。


        正则表达式:感觉很厉害,但是没咋学明白。


        随机数

        rand函数的问题:程序通常需要一个随机数源。在新标准出现之前,C 和 C++都依赖于一个简单的 C库函数 rand 来生成随机数。此函数生成均匀分布的伪随机整数,每个随机数的范围在 0 和一个系统相关的最大值〈至少为 32767) 之间。rand 函数有一些问题: 即使不是大多数,也有很多程序需要不同范围的随机数。一些应用需要随机浮点数。一些程序需要非均匀分布的数。而程序员为了解决这些问题而试图转换 rand 生成的随机数的范围、类型或分布时,常常会引入非随机性。

        定义在头文件 random 中的随机数库通过一组协作的类来解决这些问题: 随机数引擎类 (random-number engines) 和随机数分布类 (random-number distribution)。一个引擎类可以生成 unsigned 随机数序列,一个分布类使用引擎类生成指定类型的、在给定范围内的、服从特定概率分布的随机数。

        标准库定义了多个随机数引擎类,区别在于性能和随机性质量不同。每个编译器都会指定其中一个作为 default_random engine 类型。此类型一般具有最常用的特性。

Engine e; // 默认构造函数,使用该引擎的默认种子
Engine e(s); // 使用整型值s作为种子
e.seed(s) // 使用种子s重置引擎的状态
e.min() 
e.max() // 此引擎可生成的最大值/最小值
Engine::result_type // 此引擎生成的unsigned整形类型
e.discard(u) // 将引擎推进u步,u的类型为unsigned long long

        对于大多数场合,随机数引擎的输出是不能直接使用的,这也是为什么早先我们称之为原始随机数。问题出在生成的随机数的值范围通常与我们需要的不符,而正确转换随机数的范围是极其困难的。

        分布类型和引擎:为了得到在一个指定范围内的数,我们使用一个分布类型的对象。

uniform_int_distribution<unsigned> u(0,9); //初始化一个生成随机数0到9的分布
default_random_engine e;
int a=u(e); // 调用u的函数调用运算符

类似引擎类型,分布类型也是函数对象类。分布类型定义了一个调用运算符,它接受一个随机数引擎作为参数。分布对象使用它的引擎参数生成随机数,并将其映射到指定的分布。

        注:当我们说随机数发生器时,是指分布对象和引擎对象的组合。

        比较随机数引擎和rand函数:调用一个default_random_engine对象的输出类似rand的输出。

        引擎生成一个数值序列:随机数发生器有一个特性经常会使新手迷惑: 即使生成的数看起来是随机的,但对一个给定的发生器,每次运行程序它都会返回相同的数值序列。序列不变这一事实在调试时非常有用。但另一方面,使用随机数发生器的程序也必须考虑这一特性。

vector<unsigned> bad_randVec (){default_random_engine e;uniform_int_distribution<unsigned> u(0,9);vector<unsigned> ret;for (size_t i= 0; i<100; ++i)ret.push_back(u(e));return ret;
}
但是,每次调用这个函数都会返回相同的 vector:vector<unsigned> v1 (bad_randVec());
vector<unsigned> v2 (bad_randVec());

编写此函数的正确方法是将引擎和关联的分布对象定义为static的。由于e和u是 static 的,因此它们在函数调用之后会保持住状态。第一次调用会使用u(e) 生成的序列中的前 100 个随机数,第二次调用会获得接下来 100 个,依此类推。

        为什么随机数发生器会生成相同的随机数序列:因为随机数引擎其实都是伪随机数引擎,输入相同的数(种子)后算法始终会得到确定的结果。而在上面的代码中,不提供种子的情况下,就会使用默认的种子比如1,由于输入相同当然始终得到相同的序列。

        设置随机数发生器种子:为引擎设置种子有两种方式: 在创建引擎对象时提供种子,或者调用引擎的 seed 成员。

        剩下的内容我就略过了。。。


        命名空间概述:大型程序往往会使用多个独立开发的库,这些库又会定义大量的全局名字,如类、函数和模板等。当应用程序用到多个供应商提供的库时,不可避免地会发生某些名字相互冲突的情况。多个库将名字放置在全局命名空间中将引发命名空间污染 (namespace pollution ) 。传统上,程序员通过将名字命名的很长来解决问题,但这种解决方式显然不够理想。命名空间(namespace) 为防止名字冲突提供了更加可控的机制。命名空间分割了全局命名空间,其中每个命名空间是一个作用域。通过在某个命名空间中定义库的名字,库的作者〈以及用户) 可以避免全局名字固有的限制。

        命名空间定义:略。

        注:只要能出现在全局作用域中的声明就能置于命名空间内。

        注:命名空间的名字也必须在定义它的作用域内保持唯一。命名空间既可以定义在全局作用域内也可以定义在其他命名空间中,但是不能定义在函数或类的内部。

        注:命名空间作用域后面无需分号

        每个命名空间都是一个作用域:“定义在某个命名空间中的名字,一旦被声明,就可以被该命名空间内、在其声明之后的其他成员直接访问,也可以被这些成员内嵌作用域中的任何单位访问。”

         命名空间可以是不连续的:namespace abc{}可能是在定义一个名为abc的新命名空间,也可能是在为已经存在的命名空间添加一些新成员。命名空间的定义可以不连续的特性使得我们可以将几个独立的接口和实现文件组成一个命名空间。此时,命名空间的组织方式类似于我们管理自定义类及函数的方式:        

        命名空间的一部分成员的作用是定义类,以及声明作为类接口的函数及对象, 则这
些成员应该置于头文件中,这些头文件将被包含在使用了这些成员的文件中。

        命名空间成员的定义部分则置于另外的源文件中。

        在程序中某些实体只能定义一次: 如非内联函数、静态数据成员、变量等,命名空间中定义的名字也需要满足这一要求,我们可以通过上面的方式组织命名空间并达到目的。这种接口和实现分离的机制确保我们所需的函数和其他名字只定义一次, 而只要是用到这些实体的地方都能看见对于实体名字的声明。

        定义命名空间成员:假定作用域中存在何时的声明语句,则命名空间中的代码可以使用同一命名空间定义的名字的简写形式。也可以在命名空间定义的外部定义该命名空间的成员。命名空间对于名字的声明必须在作用域内,同时该名字的定义需要明确指出其所属的命名空间。尽管命名空间的成员可以定义在命名空间外部, 但是这样的定义必须出现在所属命名空间的外层空间中。换句话说,我们可以在命名空间内部或全局作用域中定义成员,但是不能在一个不相关的作用域中定义这个运算符。若定义放在了局部作用域内,那么它的生命周期也就限于局部作用域内。

        模板特例化:模板特例化必须定义在原始模板所属的命名空间中。和其他命名空间名字类似, 只要我们在命名空间中声明了特例化, 就能在命名空间外部定义它了。

        全局命名空间:全局作用域中定义的名字(即在所有类、函数及命名空间之外定义的名字) 就是定义在全局命名空间 (global namespace) 中。全局命名空间以隐式的方式声明,并且在所有程序中都存在。全局作用域中定义的名字被隐式地添加到全局命名空间中。作用域运算符同样可以用于全局作用域的成员,因为全局作用域是隐式的,所以它并没有名字。::member_name表示全局命名空间中的一个成员。

        嵌套的命名空间:嵌套的命名空间同时是一个嵌套的作用域,它嵌套在外层命名空间的作用域中。嵌套的命名空间中的名字遵循的规则与往常类似: 内层命名空间声明的名字将隐藏外层命名空间声明的同名成员。在嵌套的命名空间中定义的名字只在内层命名空间中有效,外层命名空间中的代码要想访问它必须在名字前添加限定符。

        嵌套命名空间:通过在第一次定义命名空间的地方将命名空间定义为inline的,可以得到内联命名空间。内联命名空间中的名字可以直接被外层命名空间使用。

        未命名的命名空间:未命名的命名空间 (unnamed namespace) 是指关键字 namespace 后紧跟花括号括起来的一系列声明语句。未命名的命名空间中定义的变量拥有静态生命周期: 它们在第一次使用前创建,并且直到程序结束才销毁。

        一个未命名的命名空间可以在某个给定的文件内不连续,但是不能跨越多个文件。每个文件定义自己的未命名的命名空间,如果两个文件都含有未命名的命名空间,则这两个空间互相无关。在这两个未命名的命名空间中可以定义相同的名字,并且这些定义表示的是不同实体。如果一个头文件定义了未命名的命名空间,则该命名空间中定义的名字将在每个包含了该头文件的文件中对应不同实体。

        定义在未命名的命名空间中的名字可以直接使用,毕竟我们找不到什么命名空间的名字来限定它们;同样的,我们也不能对未命名的命名空间的成员使用作用域运算符。

        未命名的命名空间中定义的名字的作用域与该命名空间所在的作用域相同。如果未命名的命名空间定义在文件的最外层作用域中, 则该命名空间中的名字一定要与全局作用域中的名字有所区别。

        和所有命名空间类似,一个未命名的命名空间也能典套在其他命名空间当中。此时,未命名的命名空间中的成员可以通过外层命名空间的名字来访问。

        命名空间的别名:

namespace abcdefghijklmn{}
namespace a=abcdefghijklmn; //给命名空间abcdefghijklmn设定一个短的多的别名

        using声明:一条using声明语句一次只引入命名空间的一个成员。using 声明引入的名字遵守与过去一样的作用域规则: 它的有效范围从 using 声明的地方开始,一直到 using 声明所在的作用域结束为止。在此过程中,外层作用域的同名实体将被隐藏。未加限定的名字只能在 using 声明所在的作用域以及其内层作用域中使用。在有效作用域结束后,我们就必须使用完整的经过限定的名字了。

        using指示:using指示后接一个命名空间,使命名空间内的所有成员全部可见。一直到using指示结束都可以直接使用。

        using指示和作用域:using指示引入的名字远比using声明引入的名字复杂。using指示并非拷贝粘贴,它更像是在作用域之间建立了一个临时的快捷方式。using指示会让编译器在进行名字查找时额外多了一个搜索的地方。using指示引入的名字,其优先级被视为与using指示外层作用域中的名字相同。(注意二义性问题!)如果头文件在其顶层作用域中含有using指示或using声明,则会将名字注入到所有包含了该头文件的文件中。

        注:避免using指示,一次性注入某个命名空间内的所有名字,这种做法看似简单实则充满了风险。

        ADL堂堂登场:ADL,即Argument-Dependent Lookup,意味与实参相关的查找。当调用一个没有使用命名空间限定的函数时,编译器除了“在当前作用域中寻找合适的函数,接着在外层作用域中查找”,还会“智能地”到函数实参类型所属地命名空间里去查找。接下来是书上的相关叙述:

        对于命名空间中名字的隐藏规则来说有一个重要的例外,它使得我们可以直接访问输出运算符。这个例外是,当我们给函数传递一个类类型的对象时,除了在常规的作用域查找外还会查找实参类所属的命名空间。这一例外对于传递类的引用或指针的调用同样有效。

        

std::string s;
std::cin>>s;
operator>>(std::cin,s); //等价形式

        在此例中, 当编译器发现对 operator>>的调用时,首先在当前作用域中寻找合适的函数,接着查找输出语句的外层作用域。随后,因为>>表达式的形参是类类型的,所以编译器还会查找 cin 和 s 的类所属的命名空间。也就是说,对于这个调用来说,编译器会查找定义了 istream 和 string 的命名空间 std。当在 std 中查找时,编译器找到了string 的输出运算符函数。假如没有这样的规则,则我们必须为输出运算符专门提供一个using声明,或者使用函数调用的形式调用输出运算符并把命名空间的信息包含进来。

        查找和std::move和std::forward:因为这两者的参数都是万能引用的形式,所以可以匹配任何类型。故极易发生二义性冲突。因此,使用时尽量用std::move而不是move。

        友元声明和ADL:一个在类内部进行的、针对非成员函数的友元声明,除了授予友元权限外,还有一个重要的副作用:如果这个函数不是之前已经在该命名空间中被声明过的,它会将这个函数“注入”到外围的命名空间中,使其成为ADL的候选函数。(此时可以把友元当作它最近的外层命名空间的成员,虽然它只能通过ADL查找到)。

        注:如果函数参数是一个派生类,那么ADL会在派生类和其基类所属的命名空间中去进行查找。

        重载和using声明:当我们为函数书写using声明时,该函数的所有版本都被引入到当前作用域中。一个 using 声明引入的函数将重载该声明语句所属作用域中已有的其他同名函数。如果 using 声明出现在局部作用域中,则引入的名字将隐藏外层作用域的相关声明。如果 using 声明所在的作用域中已经有一个函数与新引入的函数同名且形参列表相同,则该using 声明将引发错误。除此之外,using 声明将为引入的名字添加额外的重载实例,并最终扩充候选函数集的规模。

        重载和using指示:using指示将命名空间中的成员提升到外层作用域中,并且与外层作用域中成员优先级相同。与using 声明不同的是,对于 using 指示来说,只要我们指明调用的是命名空间中的函数版本还是当前作用域的版本,引入一个与已有函数形参列表完全相同的函数并不会产生错误。而对using声明来说,一旦发现有与已有函数形参列表完全相同的函数则马上报错。

        为什么有这样的不同:using声明的核心机制为名字注入,它更像是真正的声明。而using指示所做的只是让编译器在当前作用域结束时,如果仍然找不到对应的名字的话也来命名空间内看看,它并没有将成员直接注入到当前作用域中。


        运行时类型识别:由typeid运算符和dynamic_cast运算符实现。适用于通过基类引用或指针执行派生类中的方法。(虽然一般情况应该直接用虚函数,但也有少数情况不行)

        dynamic_cast:使用dynamic_cast可以强行执行向上/下转型,使用方式为dynamic_cast<type*> (e); 其中e必须是有效指针,type*也可以是type&或type&&。如果转化目标是指针并且失败了,会返回0。如果转换目标是引用并且失败了,会抛出异常bad_cast。

        typeid:和decltype不同,typeid返回的是一个对象。这也使typeid可以这样用,typeid(a)==typeid(b)。typeid作用于引用时,忽略引用。typeid忽略顶层const,typeid作用域数组或函数时返回数组或函数类型。如果 typeid 的操作数是一个非多态类型的表达式,或者是一个类型名,或者是指针或引用本身时,那么 typeid 会在编译时(Compile-time)进行求值。它不会在运行时进行任何检查,而是直接返回该表达式的静态类型(Static Type)。如果 typeid 的操作数是一个多态类型的左值、,那么 typeid 会在运行时(Run-time)进行求值。它会沿着虚函数表指针(vptr)查找,以确定该表达式所引用的对象的真实动态类型(Dynamic Type)。

        type_info类:略


        枚举类型:分为限定作用域的枚举类型和不限定作用域的枚举类型,前者用enum class定义,后者用enum直接定义。前者枚举成员的名字被严格限制在枚举类型自身的作用域内。你必须使用类型名 :: 的方式来访问枚举成员。而后者的枚举成员则处于有效的作用域中,可以直接访问。

        枚举成员:枚举成员是 const, 因此在初始化枚举成员时提供的初始值必须是常量表达式 。也就是说,每个枚举成员本身就是一条常量表达式,我们可以在任何需要常量表达式的地方使用枚举成员。 例如, 我们可以定义枚举类型的 constexpr 变量。

        指定enum的大小:尽管每个enum都定义了唯一的类型,但是enum实际上是由某种整数类型来表示的。我们可以在enum的名字后面加上冒号和我们想在enum中使用的类型。(默认为int)

        和类一样,枚举也定义新的类型:虽然enum实际上由int表示,但是并不意味着枚举类型和int型可以隐式转换。但是枚举类型可以隐式转换为整形。

        枚举类型的前置声明:


        类成员指针:成员指针 (pointer to member) 是指可以指向类的非静态成员的指针。一般情况下,指针指向一个对象,但是一个类成员指针,它指向的不是内存地址,而是一个类的特定成员。它本身是一个偏移量(offset)或标识符,只有当它被应用(apply)到一个具体的对象实例上时,才能最终定位到那块内存。类的静态成员不属于任何对象,因此无须特殊的指向静态成员的指针,指向静态成员的指针与普通指针没有什么区别。成员指针的类型襄括了类的类型以及成员的类型。

        当初始化一个这样的指针时,我们令其指向类的某个成员,但是不指定该成员所属的对象, 直到使用成员指针时,才提供成员所属的对象。

        指向数据成员的指针:

        声明:Type ClassName::*pointer_name

        获取地址:&ClassName::member_name

        使用:object.*pointer_name(任意对应类型的实例都可以通过该指针来调用指针指向的数据成员) 或 pointer_to_object->*pointer_name

        成员函数指针:

        初始化:auto f=&ClassName::member_function_name;

        使用:(object.*pointer_name)(args...) 或 (pointer_to_object->*pointer_name)(args...)

        方便记忆,对于这两者*pointer都可以替换为变量名,函数名。


        嵌套类:一个类定义在另一个类的内部,称为嵌套类。嵌套类是一个独立的类,与外层类基本没什么关系。体现在:嵌套类的定义与外层类无关,就像是在任何其他地方定义了一个类一样。只是根据访问权限有可能只有外层类才能创建一个嵌套类对象(嵌套类对象为private或protected),或者在外层类的外层可以通过Outer::Inner来定义一个嵌套类。(嵌套类对象为public)是的,访问嵌套类的方式和访问静态成员类似,毕竟二者都不属于对象,属于外层类本身。

        注:定义嵌套类只是定义了一种类型,而非定义了一个实例!

        具体一点:嵌套类定义中不包含外层类成员,假如在外层类的一个方法中定义了一个嵌套类对象,那么该方法对嵌套类成员的访问权限也由嵌套类成员的访问限定符决定。外层类的成员和外层类的友元对嵌套类的成员的访问权限相同。


        局部类:类可以定义在函数的内部,这样的类叫局部类。局部类的所有成员都必须完整定义在类的内部。局部类不允许声明静态数据成员。局部类对其外层作用域中名字的访问权限受到很多限制,局部类只能访问外层作用域定义的类型名、静态变量以及枚举成员。如果局部类定义在某个函数内部,则该函数的普通局部变量不能被该局部类使用。局部类可以通过 ::sth 的形式访问一个全局对象。


文章转载自:

http://NtPHSLa7.jgmbx.cn
http://nQnd7yXC.jgmbx.cn
http://pkdFwWKG.jgmbx.cn
http://8MjH0YVY.jgmbx.cn
http://9CMS5DoQ.jgmbx.cn
http://oIhv7hIQ.jgmbx.cn
http://v7BVFcc4.jgmbx.cn
http://jOMiXNAb.jgmbx.cn
http://FTZ6VTML.jgmbx.cn
http://jt9lF2th.jgmbx.cn
http://3OPntinP.jgmbx.cn
http://m6RkGyrM.jgmbx.cn
http://CwuSipWA.jgmbx.cn
http://nUgdU9VF.jgmbx.cn
http://PztNlni7.jgmbx.cn
http://TZIifRsC.jgmbx.cn
http://XjzLOU8A.jgmbx.cn
http://0chJsye7.jgmbx.cn
http://yP8oxBW9.jgmbx.cn
http://y2pJ7Sgj.jgmbx.cn
http://N0QPBLGy.jgmbx.cn
http://KWH9XMGf.jgmbx.cn
http://9EsgYQZm.jgmbx.cn
http://QmXK3IVA.jgmbx.cn
http://GTk6zDD3.jgmbx.cn
http://1BoTbRf0.jgmbx.cn
http://8xSQVlsN.jgmbx.cn
http://ysulfizy.jgmbx.cn
http://fmjWYpcP.jgmbx.cn
http://0vzBDYFZ.jgmbx.cn
http://www.dtcms.com/a/373283.html

相关文章:

  • 【AI】AI 评测入门(二):Prompt 迭代实战从“能跑通”到“能落地”
  • 经验分享:如何让SAP B1数据库性能提升50%
  • kaggle_吃鸡_数据预处理随机森林
  • Excel随机金额或数字分配方法
  • cocos异步加载问题
  • Spring Boot 多数据源配置
  • 信奥赛csp初赛高频考点真题分类解析之:基本运算
  • langchain 输出解析器 Output Parser
  • [数据结构] 栈 · Stack
  • 大语言模型的链式思维推理:从理论到实践
  • C语言快速排序
  • 软件可靠性失效严重程度分类与深度解析
  • 如何让dify分类器更加精准的分类?
  • C# Web API 前端传入参数时间为Utc
  • Python爬虫实战:研究3D plotting模块,构建房地产二手房数据采集和分析系统
  • sglang pytorch NCCL hang分析
  • langchain 缓存 Caching
  • Spark生态全景图:图计算与边缘计算的创新实践
  • 最长上升/下降子序列的长度(动态规划)
  • 自动驾驶中的传感器技术38——Lidar(13)
  • 计算机组成原理:计算机的分类
  • Spark SQL解析查询parquet格式Hive表获取分区字段和查询条件
  • 辨析——汇编 shell C语言
  • 免费的SSL和付费SSL 证书差异
  • 全新 Navicat On-Prem Server 3 正式上线,数据库云管理能力全面跃升
  • 华大 MCU 串口 PWM 控制方案完整笔记
  • 档案管理软件
  • Qoder 使用说明书,公测期免费体验
  • 实现自己的AI视频监控系统-第四章-基于langchain的AI大模型与智能体应用2
  • 消息队列-初识kafka