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知识点的主要内容,如果未能解决你的问题,请参考以下文章