C++基础(齐全)
文章不算短,比较齐全,最好“ctrl+f”来寻找需要的东西。
输入输出流:标准输入输出是istream/ostream。输出:std::cout<<"hello world"<<endl; 输入:std::cin>>v1>>v2; 其中std为命名空间,指出cout和endl是定义在命名空间里的,标准库定义的所有名字都在命名空间中。
endl英语意思是end of line,即一行输出结束,然后输出下一行。
this关键字:是c++中的关键字,也是一个const指针,它指向当前对象,通过它可以访问当前对象的任何成员。
声明:使得名字为程序所知,一个文件要使用其他文件定义的名字,必须对这个名字进行声明。
定义:负责创建与名字关联的实体,会申请访问空间。
通过给字面值加上前缀或后缀的方式,指定字面值的类型。
“变量”和“对象”一般是可以互换使用的。
引用‘&’
引用的定义:每个引用标识符标识符都必须以 '&' 开头,允许一条语句中有多个引用。引用只能绑定类型相同的对象,不能绑定子面值。引用本身不是对象,而指针是对象。引用不能改指向不同对象。
这里用一个例子了解一下对指针的引用:
int i = 42;
int *p;
int *&r = p; //r是对p指针的引用
r = &i; //给r赋值&i就相当于给p赋值&i
*r = 0; //将i的值改为了0
'&'表示引用符的时候,引用不是对象,它只是给已经存在的对象取一个别名。如:int &r = i; 表示 r 和 i 变量绑定在一起,使用 r 就和使用 i 变量得效果一模一样。
有时可以通过引用来减少复制构造函数和析构函数的使用,例如函数形参是引用时,就能很好的减少复制构造函数和析构函数的使用。
const
const一定要赋初值。
使用了const的对象,默认是在本文件内才有效的,在其它文件是无法使用这个对象的。如果其它文件要使用这个对象,必须在该文件和使用这个对象的文件都对这个对象申明extern。
常量引用可以绑定非常量类型的对象,非常量类型的引用不可以绑定常量对象:
int j = 2;
const int &temp_1 = j; //正确,const修饰的引用可以绑定非常量类型对象,反之则不行
int &temp_2 = j;
temp_1 = 3; //错误,不可改变 j 的值了
temp_2 = 3; //正确,改变了 j 的值
常量指针可以指向非常量类型的对象,非常量类型的指针不可以指向常量对象:
int i = 3;
const int *cptmep = &i; //正确,const修饰的指针可以指向非常量类型对象,反之则不行
int *ptmep = &i;
*cptmep = 3; //错误,无法赋值给i
*ptmep = 3; //正确,赋值给i
int *const p = &i; 是表示p这个指针指向的地址无法改变,但是地址中的内容是可以改变的。
constexpr
关键字:定义一个常量表达式。常量表达式是指:值不会改变,并且在编译过程就能得到计算结果的表达式。constexpr修饰的都是字面值,所谓字面值就是一些简单类型的值,复杂的比如:自定义类、IO库、string类型等都不是字面值。它和const在指针上的使用效果有时差别甚大:
const int *p = &i; //表示*p的值为常量
constexpr int *p = &i; //相当于顶层const,即修饰的对象p的值为常量,表示这个指针为常量。
const 和 constexpr 的区别:先说明一下常量吧,两个条件,(1)不能修改。(2)被编译器能算出值得表达式初始化。可以看出const修饰的变量只满足条件(1),还不能称之为常量。constexpr修饰的变量就直接是常量了,这使得程序运行效率提高。
typedef
typedef是类型别名的关键字。有另一种方式定义类型别名:using IS = my_struct; 这样就相当于 typedef my_struct IS;
下面代码解释了typedef的一个易错点:
typedef char* pstring;
const pstring tmep = 0; //是tmep这个指针的值不能改变,不能理解为:const char *tmep = 0;这里就OK。
auto
auto关键字,让编译器自动的为我们分析表达式的类型,比如:auto result = reson1 + reson2; auto可以同时定义、声明多个变量,但是必须是同类变量。
decltype
返回操作数的数据类型。比如:
decltype(fct())m = x; //定义fct()返回值类型的变量m。
const int i = 3,&j = i;
delctype(j)y = x; //y是个引用,类行为const int &,绑定x。
表达式是一个变量加上双层括号,其结果一定是一个引用,如:
decltype((variable));
表达式不是一个变量,则decltype返回表达式计算的对应结果:
int i = 2,*p = &i;
decltype(i + 0) m; //声明一个int类型的变量m
decltype(*p) ptr; //错误,引用未初始化。表达式是解引用操作时,返回结果必是引用,结果即是int &ptr。
using
我们使用 using std::cin; 之后,引入std 中的cin 成员,在后边使用到标准输入就只需要:cin>>i; 。
在头文件中尽量不使用using namespace,会引起不必要的麻烦。
在文件开头输入:using namespace std; 之后,后边自定义的对象和函数将不能和标准库中的对象和函数重名。
一篇不错的关于命名空间的博文:https://blog.****.net/fl2011sx/article/details/51308529
标准库类型string
string是定义在标准头文件中的一个类,类的初始化可以有很多种,都是由它本身的构造函数决定。
我们常用的初始化方式如下:
(1)string s; //s采用默认初始化,为空字符串
(2)string s = s1; //s为s1的拷贝
(3)string s(s1); //s为s1的拷贝
(4)string s = "value"; //s为“value”
(5)string s ("value"); //同上
(6)string s(n,'c'); //s字符串的值为n个c
输入数据给string类型的字符串时,空格的存在要格外小心,如下:
string s1,s2;
cin >> s1 >> s2; //假设输入:" hello world " , 中间有空格将被认为是两个字符串。
cout << s1 <<s2 <<endl; //输出结果是:helloworld
值得一提的是,在C中,prinf_s()输出字符串,传入给是个地址,否则会报错。C++中,用cout <<输出char *s的字符串,可以输入指针 s 也可输入*s,输入指针 s 将输出整个字符串,输入*s将输出首字符。
getline()函数。string s; getline(cin,s);就是从标准输入读一行字符串到 s 中,它是以 '\n' 判断一行的结束,但是得到的string对象中不包含'\n' 。
string中的成员函数:
string.empty():string对象为空时,返回值为1
string line;
while( getline(cin, line) )
if( !line.empty() )
cout << line <<endl;
string.size():表示多少个字符串,空格也算,返回值是无符号整形
string line;
while( getline(cin, line) )
if( line.size() >80 )
cout << line <<endl;
string类定义了比较字符串的运算符,定义的比较方式和 C 中一样,不累述了。
string的加减运算中,等号的左右两边的对象中,每一边都至少有一个string类型对象。这是一个错误的示例:string s3 = "hi" + "hello"; 错误原因:只有左值有string对象;两子面值不能直接相加。
范围for语句:for(declaration : expression) statement; 其中expression是一个序列,declaration是序列中的基本元素,每一次循环,declaration就被置为expression的下个元素。
范围for语句示例:
string s("hello world!!!");
for(&c : s) //每次对s字符串的一个字符按照statement操作。
c = toupper(c); //c是引用,将直接改变字符串中对应的值
cout<< s <<endl;
string对象是可以用下标来访问的,比如string[1]表示访问字符串的第2个字符。
使用一个“字符”是操作一个ASCII 码,但使用是一个“字符串”是操作指针。
标准库类型vector:
vector表示对象的集合,其中所有对象的类型都相同。vector也被称之为容器、类模板(还有其它模板比如:函数模板)。
编译器根据模板创建类 和 函数的过程叫做实例化。
vector<int> vect;创建一个int 类型的vector 模版对象,对象名为vect。
初始化:
vector<T> v1; //v1中不含任何元素,执行默认初始化,其潜在对象类型是T
vector<T> v2(v1); //将v1模板拷贝到v2
vector<T> v2 = v1; //同上
vector<T> v3(n,val); //包含了n个重复元素,每个元素的值为val
vector<T> v4(n); //包含了n个元素,被默认初始化
vector<T> v5{a,b,c...} //初始化每个元素,注意:不能写为"()"
vector<T> v6 = {a,b,c...} //同上
"()"提供个数和初始值来出初始化vector对象;"{}"直接提供初始化列表。
vector的成员函数push_back(),该函数作用是将一个值作为vector对象元素压到vector对象尾端,举例:
vector<int> v2;
for(int i = 0; i != 100; i++)
v2.push_back(i); //循环后v2的成员元素就是0到99
vector也有empty()、size()函数,其功能和string类型成员函数完全一样的。
vector类型的对象下标只能对已存在的元素进行操作,所以不能访问超出下标范围的元素,也不能添加元素。使用范围for能很好的防止访问了下标外的元素。
emplace_back和push_back类似,push_back会调用其构造函数,而emplace_back不会调用其构造函数。
迭(die)代器
迭代器到底是神魔恋?其实它是类似下标访问,但比下标访问更加通用。C++程序员就常用迭代器访问标准库模板的对象的元素,因为标准库模板都能使用迭代器,但少数标准库模板才能使用下标。
有迭代器的类型,都拥有返回迭代器的函数。begin()返回值指向模板第一个元素的迭代器,end() 返回指向模板最后一个元素的后一个元素的迭代器。
这里举个例子就能明白什么是迭代器了:
string s("hello world"); //string 也是标准库模板之一
//下行代码的c++使得迭代器指向了下一个元素。这个auto c;实际上会转换为:vector<char>::iterator c;
for(auto c = s.begin(); c != s.end() && !lsspace(*c); c++)
*c = toupper(*c); //字符变为大写,迭代器要进行解引用才能操作对象中的元素
cout << s << endl; //输出结果是"HELLO world"
创建某类型模板的迭代器,标准库模板都使用iterator 或 const_iterator来表示迭代器的类型:
vector<int>::iterator it; //能通过 it 读写vector<int>类型对象的元素,iterator it即是指的迭代器,是一种泛型指针。
string::const_iterator it2; //能通过 it2 读 string 类型对象的元素
/* 上述 it 和 it2就像指针一般,可以通过it 和 it2 当作下标来访问模板数据 */
通过vector 的对象可以调用begin()等模板的成员函数,比如begin()等。
cbegin() 是类似 begin() 的成员函数 ,其返回的迭代器是const_iterator类型。
凡事使用了迭代器的模板对象不要再向其添加元素了,因为这样会使迭代器失效。
举个vector使用迭代器的例子
数组
数组元素个数称之为维度,维度一定是常量表达式,在编译时维度一定是已知的。
数组名是一个地址常量,不能像指针一样赋值其它地址。
定义数组时不允许用auto关键字。
允许初始化时不写出维度的大小,如:a[] = {1, 2, 3}; 或者初始化的个数小于维度的值,如:a[5] = {1, 2, 3};,少的两个元素会默认初始化为0。
不存在 引用数组,但是存在 数组的引用。因为数组存放的是对象,而引用不是对象。
使用decltype和auto向数组获得类型时的区别:
int a[] = {1, 2, 3, 4};
auto c(a); //得到的 c 是int *类型,指向a[0]
decltype(a) s = {12, 13, 14, 15} //s 是一个和 a 大小类型都相同的数组
标准库中定义了begin()、end()函数,与标准库模板成员函数同名。功能也是一模一样,即是返回数组首元素地址。
数组的指针只能指向数组的元素或数组尾部元素的后一个元素,指向指针尾部元素后一元素时切记不要解引用。
数组的下标可以是有符号的,而标准库里类型的下标必须是无符号。
函数
static_cast<int>(expression),即是强制性类型转换,将expression的类型转换为int型。
std::setw :需要填充多少个字符,默认填充的字符为' '空格
std::setfill:设置std::setw将填充什么样的字符,如:std::setfill('*')
std::setbase(n):将输出数据转换为n进制
std::setprecision():控制输出流显示浮点数的数字个数,C++默认的流输出数值有效位是6。
子函数对形参的改变不会改变实参的值。
内联函数:内联函数不是在调用时发生控制转移,而是在编译时将函数体替换到每一个调用处。
有默认形参值的函数,其含有的默认形参一定写在形参括号的最后面。是可以给默认形参传入新的实参。
函数重载:函数名称相同,函数形参的个数或形参的类型不同,调用到该函数时编译器会自动选择合适的函数。这叫做函数重载。
函数返回类型是引用,那么return 的一定是指针或引用。
千万不要返回局部对象的引用 或 指针。因为return后,局部对象就被自动清理了,那么引用和指针就变成了“未知引用”和“野指针”。
return、exit、abort的区别:
(1)return,函数返回。清理局部对象(调用其析构函数),若在main中将有系统调用exit()。
(2)exit,退出进程。清理全局和静态对象,清空所有缓冲区,关闭所有I/O通道。
(3)abort,异常结束进程,不做清理直接结束。
函数只有声明没有定义会报连接错误。
枚举
枚举的作用:枚举是一种规范,它的成员代表某种含义。
枚举的声明:enum 枚举类型名 {变量参数列表}; 例子:enum weekday {SUN, MON, TUE, WED, THU, FRI, SAT};
枚举元素中每个元素都是一个常量,声明后便不能赋值。默认状态,每个元素值依次是0, 1, 2, 3...
声明时可以人为赋值:enum weekday {SUN = 7, MON = 2, TUE, WED, THU, FRI, SAT}; 之后元素值为MON的基础+1。
对枚举类型的变量赋值要将其转换为枚举类型才行,比如:
enum weekday{SUN, MON, TUE, WED, THU, FRI, SAT};
weekday now_weekday;
int count = 0;
now_weekday = count; //错误,要转换为枚举类型才能赋值
now_weekday = weekday(count); //正确,now_weekday 变量值为SUN
类和对象
面对对象编程的精髓:
抽象:将对象抽象为数据和行为,其中行为的直白理解就是,写段代码来操控数据完成功能。
封装:将逻辑上相关的数据和功能相结合形成一个整体,比如“类”。
继承:类有继承机制,使得程序员可以在类的特性基础上添加更多详细内容。
多态:核心精华就是 虚函数、模板。
类有public、protected、private。public成员,类外可以访问;private成员只有本类成员函数才能访问,外部函数通过类的对象是无法访问的;protected成员只有本类成员函数才能访问。和私有成员的区别是:派生新类时影响不同。
类中未声明属性的成员默认为private成员。
对象:对象即是类型的实体。
定义一个类并未分配内存,只是通知编译器该类占多大内存。定义了类的对象才会分配内存。类的指针也不会分配相关数据内存,需要new(),new()会调用构造函数。
若对象定义在循环或if 内部,则该对象的作用域是循环或if 内部,在外使用会报错。
构造函数:对象被创建时调用。给创建的新对象赋上特定的初始值。构造函数名与类名相同。一个类的构造函数可以有多个,但是形参一定是不同的。构造函数在有没有形参的情况下,称之为默认构造函数,默认构造函数不能通过括号进行调用。
复制构造函数:调用条件:(1)类的一个对象初始化该类的另一个对象时。(2)函数形参为类的对象,调用函数时,形参和实参结合时。(3)函数返回值为类的对象,函数执行完返回给调用者时。 将已存在对象的每个数据成员值复制给新对象。复制构造函数名与类名相同,形参是对象的引用。
析构函数:在类的对象生存周期结束时自动调用,做一些清理工作比如释放内存,析构函数不接受任何参数。动态分配的内存需要手动 delete,其余的变量将自动释放。
C++编译器规定:类中,“对象”成员变量的初始化动作一定发生在进入构造函数之前。也就是说明,如果“对象”的成员变量没有在初始化列表中初始化,那么在构造函数对其赋值时,编译器将会多调用一次该对象的默认构造函数,然后再调用该对象的复制构造函数对其赋值。
类的组合:就是在类中含有其他类的对象。
构造函数也可以这样写:
//前面的p1、p2是形参。后面的,now_p1(p1)表示初始化now_p1和now_p2
Line::Line(point p1, point p2):now_p1(p1), now_p2(p2)
{
... //初始化Line类型的数据成员
}
类中较为复杂的函数是在外面声明:返回值类型 类名::函数名(形参) {函数代码}
类的静态成员
静态成员是为了解决一个类的不同对象中数据和函数共享问题,分为静态数据成员和静态函数成员。
静态数据成员:数据类型前使用static声明(全局变量),类中定义的是引用型声明。(1)静态数据成员,需在类外进行初始化与定义:类型名 类名::数据名 = 值。(2)静态const数据成员可以在类中进行初始化,也可以在类外进行初始化和定义。静态数据成员的作用域是整个类,而不是某个对象。储存在静态存储区。静态数据与非静态数据不能重名。
所有的静态变量只能被初始化一次。静态变量后紧接的32bit的内容表示该变量的地址和变量的状态(是否被初始化)。
静态函数成员:函数前使用static声明(全局函数)。它可以在没有定义对象时直接访问到静态数据和静态函数。可通过类名限定进行调用,也可通过对象进行调用。
访问非静态函数只能通过对象访问。
类的友元
友元函数:不是类成员函数,在类中要用关键字friend声明一下该函数。可以通过类的对象访问类的私有和保护成员。
友元类:A类是B类的友元类,A中所有成员函数都是B的友元函数,可以通过对象访问B的私有和保护成员。
类的继承和派生
类的继承和派生是指的一个过程,都是指已有类产生新类的过程。其中的已有类称为基类,新类称为派生类。
派生类除了基类的所有成员外还有新增的成员。
派生类继承到的基类成员的属性将取决于继承方式,所谓属性就是public、protect、pravate,继承方式有公有继承、私有继承、保护继承三种。
派生类如果有和基类同名的函数,那么基类的函数将会被覆盖。
公有继承:基类公有成员和保护成员在派生类中属性不变。基类私有成员不可直接访问。(常规选择)
私有继承:基类公有成员和保护成员在派生类中变为私有成员。基类私有成员不可直接访问。
保护继承:基类公有成员和保护成员在派生类中变为保护成员。基类私有成员不可直接访问。
前边说过,保护类成员和私有成员的区别在派生新类中体现,从上边3类继承方式就可看出来:如果 类B 由 基类A 私有继承得到,那么 类B的派生类C 的成员函数将无法使用 类A 的所有数据成员。但如果 类B 由 基类A 保护继承得到,结果就不同,类B的派生类C 如果是公有或保护继承,那么类C还可使用类A的部分数据成员。
派生类构造函数在类外定义时采用如下定义格式:基类名1(形参), 成员对象名1(形参), 成员对象名2(形参), 基类名2(形参)... {函数体}。
基类构造函数有形参,那么其派生类构造函数就必须要有声明。
上述格式中紫色字体为初始化列表,初始化列表是用来说明类中需要使用构造函数进行初始化的内容,未说明的表示采用默认初始化。
派生类构造函数执行顺序:开始调用基类的构造函数,采用定义派生类时声明基类的顺序。然后初始化对象,采用他们派生类中声明的顺序。最后执行函数体。
所以构造函数执行顺序和初始化列表无关,初始化列表是无序的。
派生类析构函数:对新增的非对象成员的清理做好就行了。
派生类析构函数的执行顺序和派生类构造函数的执行顺序相反。
派生类析构函数会默认清理栈中成员,对堆中动态分配内存还需手动释放。
类型兼容规则
派生类对象可以隐式转换为基类对象。
派生类对象可以初始化基类引用。
派生类对象地址可以隐式转换为基类指针。
类的同名问题
如果派生类中声明了基类同名成员,那么基类同名成员将会被覆盖。其中对于同名函数成员只要函数同名,哪怕形参表不同也会被覆盖。想要访问到基类同名函数,可以通过作用域分辨符 "::" 访问到基类的同名成员。
派生类的某几个基类中含有同名函数,哪怕函数参数是不同的,想调用某个基类的函数就需使用作用域分辨符 "::" 。
虚基类:直接基类在继承时将共同基类设置为虚基类,从共同基类派生出的多个直接基类那继承来的同名数据成员在内存中就只有一个备份,同名函数成员在内存中只有一个映射。
有using关键字声明的函数不加"()"。
这里有个自己的同名覆盖总结:同一个类中同名但只要形参不同,编译器能区分。不同类中一旦同名无论形参如何,编译器不能区分,会有覆盖策略。
多态性
运算符重载:对象是不可以直接进行运算的,而我们有时需要有类似的功能,所以就自己定义函数来完成类似运算符的功能,这个函数多半在类中定义。
一般语法:
返回类型 operator运算符(形参)
{
}
举个例子,假设有两个对象A、B,那么A+B就相当于,调用A中的operator+函数并将B当作形参传给函数进行运算,即是A.operator+(B)。运算符重载函数虽然是A中的函数,但是可以堆B中私有成员进行操作。
绑定即是把一段程序和对象结合在一起的过程。
动态绑定:在编译链接阶段完成。
静态绑定:在程序运行阶段完成。
运算符重载函数也可以在类外定义。
多态的实现是通过指针或引用,通过对象的转换只会造成对象切割。
虚函数:当把基类的某个成员函数声明为虚函数后,允许在其派生类中对该函数重新定义,赋予它新的功能,并且可以通过指向基类的指针指向同一类族中不同类的对象,从而调用其中的同名函数(注:如果不是虚函数则无法通过基类指针访问到派生类的)。由虚函数实现的动态多态性就是:同一类族中不同类的对象的指针,对同一函数的调得到不同的结果。
虚函数声明:virtual 函数类型 函数名(形参)。
如果派生类中的函数的名字、形参个数、形参类型都和基类声明的虚函数相同,那么派生类中这个函数将确定为虚函数,并覆盖基类虚函数。
有虚析构函数,没有虚构造函数。
纯虚函数就是在基类中并没有定义出具体操作内容,声明格式:虚函数声明:virtual 函数类型 函数名(形参) = 0;含有纯虚函数类就是抽象类。
覆盖即是虚函数的作用,重载和覆盖的区别:
成员函数被重载的特征有: 1) 相同的范围(在同一个类中);2) 函数名字相同;3) 参数不同;4) virtual关键字可有可无。
覆盖的特征有: 1) 不同的范围(分别位于派生类与基类);2) 函数名字相同;3) 参数相同;4) 基类函数必须有virtual关键字。(其实就是多态)
template <class T>
这是模板。
应该将模板的定义和实现都放入.h 文件中,这和编译器、链接器有关,详细可以参见大牛的博文:https://blog.****.net/u010273652/article/details/21568131(写的很通俗易懂,很详细)
加入有两个类功能相同,只是处理的传入参数类型不同。我们就没不要写两个不同的类,而是声明为一个模板即可:
template<typename numtype> //声明一个模板,虚拟类型名为numtype
class Compare //类模板名为Compare
{
public :
Compare(numtype a,numtype b)
{
x=a;y=b;
}
numtype max( )
{
return (x>y)?x:y;
}
numtype min( )
{
return (x<y)?x:y;
}
private :
numtype x,y;
};
/* 声明一个对象Compare<int > cmp1(3,7); */
C++ Map常见用法说明
推介博文:https://blog.****.net/shuzfan/article/details/53115922
Map是一种键值对容器。
内联函数
内联函数:内联函数实现的代码,直接替换到被调用位置处的函数。inline 只是对编译器的一种建议。
符合内敛函数的条件:编译器能看到被 inline 声明的某函数,并能在当前文件(随处可见)找到该函数的定义。(类似于:声明加inline,调用不加inline 这样的一些特殊情景,其结果总从这句话中找到答案)
内敛函数为什么能提高函数效率:普通函数的调用时,程序将“保存现场”并转到存放该函数的内存中的某个地址执行,当函数调用完毕后,“恢复现场”并返回去继续执行调用函数后的代码。而这里的“保存现场”“恢复现场”对时间有开销。而使用内联函数就不会有存在这部分的开销。
和宏的区别:(1)宏是预处理器进行替换;内联是编译器进行替换。(2)由于宏是字符串替换,而内联是嵌入代码,所以内联函数想比宏的使用会少很多麻烦,对函数处理优于宏。
类中直接定义了函数体的函数都默认为内联函数。
智能指针
智能指针实际就是模板:将基本类型指针封装为类对象指针(这个类是个模板,以适应不同基本类型的需求),并在模板析构函数里编写delete语句删除指针指向的内存空间。如果使用智能指针,不用担心忘记释放内存,而产生内存泄漏。
有 4 种智能指针:auto_ptr(非共享所有权)、unique_ptr(非共享所有权)、shared_ptr(共享所有权)和weak_ptr(共享所有权)。其中auto_ptr 是在c11 中已禁止使用的。
一篇通熟易懂的博文,介绍了为什么auto_ptr 被禁用,以及 auto_ptr、unique_ptr、shared_ptr 之间的区别:http://www.cnblogs.com/lanxuezaipiao/p/4132096.html
多态和对象切割问题
子类和基类的,对象之间以及指针之间转换的各种情况:https://blog.****.net/sszgg2006/article/details/7816725
动态转型函数 dynamic_cast:该函数的作用即是类型转换,是一种基于能力查询的转换。
使用方法:
T1 obj;
T2* pObj = dynamic_cast<T2*>(&obj); //转换为T2指针,失败返回NULL
T2& refObj = dynamic_cast<T2&>(obj); //转换为T2引用,失败抛出bad_cast异常
dynamic_cast要求转换的对象obj 必须带有虚函数。解释:该函数需要进行运行时的数据检查,而这个数据存储在虚函数表。所以只有定义了虚函数才有虚函数表。
通过dynamic_cast 转换可以实现:基类指针转换为子类,然后通过返回的子类指针调用子类特有的成员函数(可以非虚)。
函数签名:用于识别不同的函数,包含了函数的信息,比如:函数名、参数个数、参数类型、参数顺序、类空间、命名空间。(在Effective C++中自定义函数签名还包含返回值)
find函数
十分简单的例子。打印出的值是0。
#include <string>
#include <iostream>
using namespace std;
int main()
{
string s("1a2b3c4d5e6f7g8h9i1a2b3c4d5e6f7g8ha9i");
string::size_type position;
position = s.find("1a");
if (position != s.npos)
{
cout << "position is : " << position << endl;
}
else
{
cout << "Not found the flag" << endl;
}
return 0;
}
delete 和 delete[]
A *a = new A[10];
delete a:表示释放了a指针指向的全部内存空间,但是只调用了a[0]的析构函数。
delete [] a:表示释放了a指针指向的全部内存空间,并调用了a[0~9]的析构函数,这样对象内部自己分配的堆也能被释放了。
对指针数组进行new、delete,应该这样:
A *a = new A*[10];
for (int i = 0; i++; i<10)
{
a[i] = new A(2);
}
for(int i = 0; i<10; i++)
{
delete a[i];
//上述delete后,a[i]的内容已被释放没法访问到了,但a[i]的值和delete前的a[i]相同。
}
delete[] a;
如果上述释放过程中,若无for循环只有delete[] a;结果会造成内存泄漏,实际上a[i]没被释放(测试方法:在释放前,定义一个变量tmp保存a[i],发现释放后tmp依旧可以访问)。
实际delete[] a;后,a指针将指向一个随机值,而不是NULL或者CDCDCDCD、DDDDDDDD(后两个是野指针默认地址)。
NULL、0、nullptr
以往的C++规范中NULL即是0,那时我们更推介使用0来表示空指针,因为容易避免一些遗漏错误。
在C++11中,为了使空指针和0区分出来,产生了nullptr。
Core文件
程序崩溃时,会在指定目录产生core文件,其主要是内存映像,可以通过gdb进行调试。
大牛博客:https://www.cnblogs.com/dongzhiquan/archive/2012/01/20/2328355.html
C++11
尾置指针:
相当于将返回值写到了“->”后面。
正常函数:
int (*func(int i))[10]
尾置指针:
int func(int i)->(*)[10]
lambda:
C11对匿名函数的支持,简单的比如:
[](int x, int y) { return x + y; } // 隐式返回类型
[](int& x) { ++x; } // 没有return语句 -> lambda 函数的返回类型是'void'
[]() { ++global_x; } // 没有参数,仅访问某个全局变量
[](int x, i nt y) -> int { int z = x + y; return z; } //使用了尾置指针
std::future:
用来获得异步任务的结果。
std::bind:
起到绑定函数和参数的作用。
std::function:
可调用对象模板类。std::function<Layer*()>代表一个可调用对象,接收0个参数,返回Layer*。
std::make_shared
用来构造智能指针的,std::make_shared<>(),在<>中的是类型,在()中的是具体的值
std::using
常用的如命名空间,这里就不多做解释了。C11有新的用法:(1)取代typedef。(2)让父类同名函数在子类中以重载方式使用。
std::result_of
没有赋值的时候,auto就失去功效了,但是result_of依然好用。
对于 F(Arg1, Arg2... Arg3) ,其中F 是一个可调用对象f 的类型,可调用对象指的是:函数指针,函数引用, 成员函数指针,或者是函数对象,Arg是其参数arg1, arg2 的类型。那么reslut_of <Func(Arg1, Arg2... Arg3)> :: type 表示的也就是 f(arg1,arg2 ... argn)的返回值的类型。
template<class Func, class Arg>
typename result_of<Func(Arg)> ::type fcall(Func func, Arg arg)
{
return func(arg);
}
泛化之美:
template <class... T>
也就是c中可变参。
std::queue
std标准库中的模版队列,主要的操作有:
入队:q.push(x);
出队:q.pop();
队首:q.front();
队尾:q.back();
判断对空:q.empty();
对个数:q.size();