C++primer知识点

Posted 勇二郎

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++primer知识点相关的知识,希望对你有一定的参考价值。

二十四:面向对象

(1)数据抽象:接口实现分离。

       继承:定义相似的类型

       多态:以统一的方式使用。(【父类】引用或指针调用虚函数 实现多态【动态绑定】)

       如果表达式也不是引用,也不是指针,则动态类型永远与静态类型一致。

       virtual 在基类函数中说明,子类的相同函数默认都是virtual

       子类参数列表(const/引用符后)后加上override,显示的注明改写基类的虚函数(帮助编译器检查)

(2)虚析构函数:使释放父类引用或指针对象时,即使实际是派生类的对象,也能正确的释放。析构函数的虚属性也会被继承。

       如果没有声明virtual则解析过程发生在编译时,而不是运行时。动态类型是内存中对象的类型,只有在运行时可知。

       virtual只出现在类内声明前,而不能放在类外定义处。

(3)初始化顺序  基类到派生类, 销毁顺序相反;派生类构造函数应该调用基类构造函数初始化基类的部分。(每个类负责定义各自的接口)

       基类的私有成员,派生类访问不了,但是派生类的基类部分是有的,即可以继承。

       对基类的访问权限由基类本身控制,即使对于派生类的基类部分也是如此。(如通过基类的友元可访问派生类对象中基类的部分

       friend函数没有访问权限(private等)的限制,函数中通过对象调用私有成员也可以,但是static变量有限制。静态成员遵循通用的访问控制规则。

(4)防止被继承:在类名后加final

       派生类向基类的转换,只对指针和引用有效;基类不能向派生类转换,即使一个基类的指针或引用绑定在一个派生类对象上,也不行。

       Direved a;

BASE *pb = &a;

Direved *pa = pb;//错误需要dynamic_cast或者static_cast才行

       对象之间不存在类型转换,实际是调用构造函数或者赋值运算符。派生类赋给基类,部分被切掉。

(5)虚函数的默认实参,由本次调用的静态类型决定

       含纯虚函数的类是抽象基类。负责定义接口。纯虚函数也可以有定义,但是必须在类外部。

(6)派生类成员和友元(函数体中),只能通过派生类对象访问基类的保护成员,而不能通过基类对象访问基类保护成员。

派生类向基类的转换:

       D--->B:(1)D公有继承B :用户代码可使用这种转换 (保护或私有继承,不行)

                     (2)无论什么方式继承:D的成员和友元都可以使用这种转换

                     (3)公有或保护继承:D的派生类的成员和友元可以使用D--->B的转换,(私有继承不行)

       struct和class唯一的区别是默认的成员访问说明符(public private),和默认派生访问说明符(public private);

(7)派生类的作用域,嵌套在基类的作用域内。(正常,不能在作用域内定义函数)

       一个对对象、指针、引用的静态类型决定了该对象的哪些成员是可见的

       派生类的函数不会重载基类中的函数,如果重名就是隐藏。如果virtual且完全相同,则是覆盖。避免隐藏,可以使用作用域运算符明确的指定。

函数名相同:

virtual                       同参

不同|重载(作用域区分)

覆盖1

隐藏2

隐藏3

隐藏4

3,4没virtual,是静态类型

2 如果通过父类的引用或指针调用子类不同参数的函数,访问不到。所以编译阶段可以发现,只能调用父类自己的函数,也是静态类型。

 

子类函数覆盖父类函数,必须完全相同(例外:返回类型可以是各自类的引用或指针)

       如果父类有许多重载的函数,子类覆盖一个就会隐藏其他。可以使用using声明语句,添加到子类作用域,然后子类就可以定义特定的覆盖了。

 

虚析构函数可以阻止合成的移动操作,即使是=default,要定义移动的操作,为避免拷贝操作遭抑制,可以都显示的定义=default出来。

 

(8)处于继承体系中,构造函数和析构函数的执行顺序,对于派生类对象构造时,先调用该基类构造函数,此时派生类对象处于未完成状态,同理,销毁时,最后销毁基类部分,此时对象也是未完成状态,这种未完成状态,编译器认为:对象的类型在构造或析构的过程中仿佛发生了改变一样(把对象的类和构造/析构函数的类看成同一个)。

 

 

(9)继承的构造函数:(c++11)派生类可以重用其直接基类定义的构造函数:并非常规的方式继承而来,姑且称继承。

       Direaved类中usingBase::Base;参数与基类的构造函数一样。

       和普通的using声明不一样,一个构造函数的using声明不会改变该构造函数的访问级别(基类中是私有,派生类中也会使私有)

       基类的构造函数的默认实参不会被继承,而是会分成多个函数:如

基类构造函数两个参数,第二个有默认实参,则派生类中会有两个构造函数,一个两个参数,一个一个参数(对应基类有默认实参的那个)。

       类不能继承默认/拷贝/移动构造函数。这些函数按正常的规则合成。

       继承的构造函数不会作为用户定义的构造函数使用,因此,如果一个类只有继承的构造函数,那么它也将拥有一个合成的默认构造函数。

 

 

 

二十五:模板

(1)

函数模板,类模板

template<typename T>......

类型参数,非类型参数(表示一个值,必须是常量表达式),绑定到指针或引用参数的实参必须具有静态的生存期。

非类型参数:

 ----常|static 指/引  (可以)

 ----局|动态对象|引用非类型模板参数  (不可以)

template<intN,int M>.......

 

inline 和 constexpr说明符放在模板参数列表之后,返回类型之前。

template<typenameT> inline T min();

 

(2)

当我们使用模板时,编译器才生成代码。

模板的定义通常放在头文件中。(包括类模板成员函数的定义)

       与函数模板不同,编译器不能为类模板推断模板参数类型。如vector<string> vec; 类模板的名字不是一个类型名,一个实例化的类型总是包含模板参数的。(在类自己的作用域内,可以把类名当类型名,不必指定模板实参)

       定义在模板类外的成员函数必须以template开头:

template <typenameT> int A<T>::max();

       如果一个成员函数没有被使用,则它不会被实例化。

 

       如果类模板包含一个非模板友元,则友元可以访问所有模板实参。

       如果包含一个模板友元,则类可以授权给所有友元模板实例,也可以只授权给特定实例。(类型参数一样或者不一样的区别)

       特例化版本作为友元(类或函数),要有前置声明,如果是所有的模板都作为友元,那么不用前置声明。

       用模板自己的类型参数成为友元:如

template<typenameT> class Bar{

friend T;

.....

};

       模板类型别名(也要传入类型):template<typename T> using ta = pair<T,T>;

ta<string>authors;//authors是一个pair<string,string>;

       类的每个实例都有独有的static对象,所以static成员也定义为模板:(只有在使用时才实例化)template<typename T> int Foo<T>:: size = 0;

template<typenameT>TA<T>::ia="find"; 编译过程会实例化,如果类型不对,会检查到错误。

 

(3)

       默认情况下,C++假定通过作用域运算符访问的名字不是类型。所以我们要指定访问的名字是类型,要使用typename关键字。(不能使用class)

template<typenameT> typename T::type fun();

 

       成员模板不能是虚函数。

       函数模板,对于编译器不好推断的类型,可以使用显式实参(函数名后加类型参数),帮助编译器实例化模板参数。

       类模板的成员模板,在类外定义时,必须同时为类和成员提供模板参数列表。如:

template<typenameT>class Blob{

       template<typename It> Blob(It b,Ite);

}

定义时

template<typenameT>

template<typenameIt>

       Blob<T>::Blob(It b,It,e){ ... }

实例化时,只需提供类模板的实参就行,函数模板的实参编译器会自动推断:

Blob<int>ab(vi.begin(),vi.end());

 

(4)显示实例化

       当两个或多个独立编译的源文件使用了相同的模板,并提供相同的模板参数,每个文件都会有该模板的一个实例。(开销比较严重)

       可以通过显式实例化避免这种开销。

       externtemplate declaration 实例化声明

       template declaration 实例化定义。当编译器遇到实例化定义是会为其生成代码。因为不了解程序会使用那些成员,所以会实例化所有成员。

如:

声明:  externtemplate class Blob<string>;

定义:  templateclass Blob<string>;

       编译时绑定删除器,unique_ptr避免了间接调用删除器的运行时开销。

       运行时绑定删除器,shared_ptr使用户重载删除器变得方便。

 

 

(5)

       实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有const和数组到指针的转换。

类模板和函数模板可以使用默认实参。使用类模板,一定要跟尖括号<>,尤其是都提供了默认实参的情况下,<>不能丢。指定了显示模板实参的函数,可以应用正常的类型转换。如:

       long lng;

       fun(lng,1023);//错误,类型不匹配

       fun<long>(lng,1023);//正确,实例化为fun(long,long);

       指定显示模板实参(函数):如返回类型与参数列表类型都不相同,没办法推断返回类型时。

       在尖括号中给出,函数名之后,实参列表之前。如:

template<typenameT1,typename T2,typename T3> T1 sum(T2,T3);

auto val =sum<long>(i,lng);

       模板实参按由左至右的顺序与模板参数匹配,只有最尾部的显示模板实参才可以忽略,为避免错误,尽量形参与实参的顺序要一致。(形参的声明与使用顺序要一致)

 

       尾置返回类型:当返回一个迭代器元素的引用时:如

template<typenameIt>

??? & fun(Itbeg,It end){ return *beg;}

//无法推断返回类型,这个时候需要用尾置返回类型

template<typenameIt>

auto fun(It beg,It end) -> decltype(*beg) {return *beg; }

//auto如果没有会提示:一个函数不能同时具有返回类型和后指定返回类型

       如果想返回的不是引用,而是一个值,可以使用标准库的“类型转换”模板,每个模板都有一个名为type的public成员,表示一个类型。在头文件type_traits中,remove_reference

template<typenameIt>

auto fun(It beg,Itend) -> typename remove_reference<decltype(*beg)>::type{ return *beg; }

 

 

(6)

       我们不能直接定义一个引用的引用,例外情况(间接):

1:通过类型别名

2:通过模板类型参数间接定义

引用折叠只能应用于间接的这两种情况:

X& & 、X& &&、X&&&都折叠成类型 X& :左左,左右,右左都折叠成 左

X&&&& 折叠成X&& 右右折叠成 右

 

template<typenameT>

typenameremove_reference<T>::type&& move(T&&t)

{

       return static_cast<typenameremove_reference<T>::type&&>(t);

}

 

转发:定义能保持类型信息的函数参数:

1:对于模板用右值引用传参

2:非模板用std::forward<T>()(返回T&&)保持原始实参类型,传参。

如:

template<typename F,typename T1,typename T2>

void fun(F f,T1&&t1,T2 &&t2)

{

       f(std::forward<T2>(t2),std::forward<T1>(t1));

}

 

(7)

重载模板 最特例化的版本优先。

非模板版本优先模板版本。

 

可变参数模板:(允许将实参传递给构造函数)

模板参数包:表示0个或多个模板参数

函数参数包:表示0个或多个函数参数。

       用省略号指出一个参数表示一个包,class...   typename...表示类型列表。类型后面跟...表示非类型参数的列表。

如:typelate<typename T,typename... Args>void foo(const T&t,const Args&... rest);

       sizeof...运算符可以测包中有多少元素。如sizeof...(Args)

对于包:

1:可以用sizeof...测大小

2:可以后面加...扩展包(分解为扩展的元素)

如:

typelate<typenameT,typename... Args>

void foo(constArgs&... rest)

{

       foo(rest...);//扩展

}

扩展中的模式会独立的应用于包中的每个元素:

typelate<typenameT,typename... Args>

void foo(constArgs&... rest)

{

       foo(print(rest)...);//扩展,相当于foo(print(va1),print(va2)...);//注意print中没有。。。

//保持实参类型foo(std::forward<Args>(rest)...)

}

 

(8)模板特例化(对于特例化版本,自己可以重新定义函数体的实现,而不必按照模板的样式,比如compare对于char *的处理可以自己用特例化版本重新定义,因为编译器的默认特例化处理会有问题。)

       函数模板特例化:必须为每个模板参数提供实参。为指出我们正在实例化一个模板,应使用template后跟一个<>,空< > 指出我们将为原模板所有模板参数提供实参。如:

模板:

template<typenameT>int fun(const T&,const T&);

特例化:

template< >

int fun(constchar*const &p1,const char* const p2){.....}

       函数要求指向此类型const版本的引用,一个指针类型的const版本是一个常量指针,而不是指向const类型的指针。所以模板要求一个顶层指针,所以例子中我们的T为const char*(是底层const

       特例化版本本质上是一个实例,而非函数名的重载版本,所以优先级低于非模板函数。

类模板部分特例化:

       类模板的特例化不必为所有模板参数提供实参

如:(参数数目相同,类型不同)

原始版本:

template<typenameT>struct remove_reference{

       typedef T type;

}

//部分特例化版本:将用于左值引用和右值引用

template<typenameT>struct remove_reference<T &>{

       typedef T type;

}

template<typenameT>struct remove_reference<T &&>{

       typedef T type;

}

 

       特例化成员,而不是类:如:

template<typenameT> struct Foo{

       void Bar(){....}

       T mem;

}

template<>//我们正在特例化一个模板

voidFoo<int>::Bar(){//进行int的特例化处理}

       如果我们用int使用Foo时,Bar之外的成员像往常一样实例化,如果我们使用Foo<int>的成员Bar,则会使用我们定义的特例化版本。

 

       总的来说,对于特殊的处理,我觉得两种办法:

1:对特定的类型显式定义特例化版本

2:定义非模板版本的函数或类(对于类的成员就相当于重载了)

 

       标准库算法都是函数模板,标准库容器都是类模板。

 

 

 

 

二十六:四个标准库设施

bitset和新标准库设施(tuple,正则表达式,随机数)

(1)tuple类似pair,但是类型的个数和种类可以多个。快速而随意的数据结构

1)tuple的所有成员都是public的

tuple_size<tupleType>::value//表示给定tuple种类中成员的数量(tupleType可以通过decltype取得)

tuple_element(i,tupleType>::type//表示给定tuple类型中指定成员的类型

get<i>(t) //返回第i个数据成员的引用。

2)使用tuple返回多个值

 

(2)bitset是一个类模板,类似array,具有固定大小。

如:

bitset<12>bitTemp(0xbeef);

bitset<32>bitTemp(“1100”);//—>低位

 

(3)正则表达式

#include<iostream>

#include<string>

#include<regex>

using namespacestd;

 

int main()

{

       //正常的正则表达式需要一个‘\’,‘\’在正则表达式中是特殊转移字符,但是放到c++的环境中,因为‘\’又是特殊字符,所以需要两个\\

       regexr("[[:alnum:]]+\\.(cpp|cxx|cc)$",regex::icase);

       smatch results;

       string filename;

       while(cin>>filename)

       {

              if(regex_search(filename,results,r))

                     cout<<results.str()<<endl;

       }

 

       return 0;

}

 

       正则表达式的语法是否正确,是在运行时解析的;

输入序列类型:

smatch:表示string输入序列

cmatch:表示字符数组序列

wsmatch:宽字符串输入

wcmatch:宽字符数组

 

       sregex_iterator可以获得所有匹配。prefix和suffix表示当前匹配之前和之后部分的ssub_match对象。

       cout<<results.str(1)//表示打印第一个子表达式

替换:regex_replace

       用一个符号$后跟子表达式索引号来表示一个特定的子表达式。

如:

string fmt = “$2.$5.$7”

regex r(phone);//用来寻找匹配模式的regex对象

string number = “(908)555-1800”;

cout<<regex_replace(number,r,fmt)<<endl;

 

(4)随机数

随机数发生器:分布对象和引擎对象的组合;

引擎类:可以生成unsigned随机数序列。

分布类:使用一个引擎类生成指定类型的、给定范围的、服从特定概率分布的随机数。

uniform_int_distribution<unsigned>u(0,9);//生成0到9(包含)均匀分布的随机数

default_random_enginee;//生成无符号随机整数

for(size_t i =0;i<10;++i)

       cout<<u(e)<<” “;

我们传递的给分布对象的是引擎本身,因为某些分布可能需要调用引擎多次才能得到一个值。

 

       对于一个给定的发生器,每次运行程序都会返回相同的数值序列。(方便调试)

要想使每次序列不相同,可以设置随机数发生器种子seed(),或对于频繁调用的函数变量适当用static或全局变量。

       分布类:具有单一模板类型参数,表示分布生成的随机数的类型。

但bernoulli_distribution是一个普通类,而非模板,它没有模板参数。返回bool值,返回true的概率是0.5,也可以在定义的时候指定true概率值。

 

(5)

hex十六进制,oct八进制,dec十进制

showbase:显示进制 加前导字符:0x 0 

noshowbase

uppercasenouppercase

使用io对象的precision或者setprecision(iomanip文件中)操作符来改变精度。

       默认情况:精度指数字的总位数,既包括小数点之后,也包括小数点之前。

tellg(),tellp()

seekg(pos),seekp();

g版本表示正在获取数据(操作输入流)

p版本表示正在放置数据(操作输出流)

 

 

以上是关于C++primer知识点的主要内容,如果未能解决你的问题,请参考以下文章

C++primer知识点

C++primer知识点

C++primer知识点

C++primer知识点

C++primer知识点

超硬核知识:两万字总结《C++ Primer》要点!