虚函数是容许被其子类从新定义的成员函数。html
虚函数的声明:virtual returntype func(parameter);引入虚函数的目的是为了动态绑定;ios
纯虚函数声明:virtual returntype func(parameter)=0;引入纯虚函数是为了派生接口。(使派生类仅仅只是继承函数的接口)c++
防止内存泄漏。想去借助父类指针去销毁子类对象的时候,不能去销毁子类对象。假如没有虚析构函数,释放一个由基类指针指向的派生类对象时,不会触发动态绑定,则只会调用基类的析构函数,不会调用派生类的。派生类中申请的空间则得不到释放致使内存泄漏。程序员
几乎同样。i++返回的是i的值,++i返回的是i+1的值,即++i是一个肯定的值,是一个能够修改的左值。算法
reserve()用于让容器预留空间,避免再次分配内存;capacity()返回在从新进行分配之前所能容纳的元素数量。编程
一般在类外申明static成员,可是static const的整型(bool,char,int,long)能够在类中声明且初始化,static const的其余类型必须在类外初始化(包括整型数组)。数组
static的做用:安全
对变量:数据结构
1.局部变量:socket
在局部变量以前加上关键字static,局部变量就被定义成为一个局部静态变量。
1)内存中的位置:静态存储区
2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)
3)做用域:做用域仍为局部做用域,当定义它的函数或者语句块结束的时候,做用域随之结束。
注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置(从原来的栈中存放改成静态存储区)及其生命周期(局部静态变量在离开做用域以后,并无被销毁,而是仍然驻留在内存当中,直到程序结束,只不过咱们不能再对他进行访问),但未改变其做用域。
2.全局变量
在全局变量以前加上关键字static,全局变量就被定义成为一个全局静态变量。
1)内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)
2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)
3)做用域:全局静态变量在声明他的文件以外是不可见的。准确地讲从定义之处开始到文件结尾。
注:static修饰全局变量,并未改变其存储位置及生命周期,而是改变了其做用域,使当前文件外的源文件没法访问该变量,好处以下:(1)不会被其余文件所访问,修改(2)其余文件中可使用相同名字的变量,不会发生冲突。对全局函数也是有隐藏做用。而普通全局变量只要定义了,任何地方都能使用,使用前须要声明全部的.c文件,只能定义一次普通全局变量,可是能够声明屡次(外部连接)。注意:全局变量的做用域是全局范围,可是在某个文件中使用时,必须先声明。
对类中的:
1.成员变量
用static修饰类的数据成员实际使其成为类的全局变量,会被类的全部对象共享,包括派生类的对象。所以,static成员必须在类外进行初始化(初始化格式: int base::var=10;),而不能在构造函数内进行初始化,不过也能够用const修饰static数据成员在类内初始化 。由于静态成员属于整个类,而不属于某个对象,若是在类内初始化,会致使每一个对象都包含该静态成员,这是矛盾的。
特色:
1.不要试图在头文件中定义(初始化)静态数据成员。在大多数的状况下,这样作会引发重复定义这样的错误。即便加上#ifndef #define #endif或者#pragma once也不行。
2.静态数据成员能够成为成员函数的可选参数,而普通数据成员则不能够。
3.静态数据成员的类型能够是所属类的类型,而普通数据成员则不能够。普通数据成员的只能声明为 所属类类型的指针或引用。
2.成员函数
不能够同时用const和static修饰成员函数。
C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。
咱们也能够这样理解:二者的语意是矛盾的。static的做用是表示该函数只做用在类型的静态变量上,与类的实例没有关系;而const的做用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。所以不能同时用它们。
const的做用:
1.限定变量为不可修改。
2.限定成员函数不能够修改任何数据成员。
3.const与指针:
const char *p 表示 指向的内容不能改变。
char * const p,就是将P声明为常指针,它的地址不能改变,是固定的,可是它的内容能够改变。
本质上的区别是,指针是一个新的变量,只是这个变量存储的是另外一个变量的地址,咱们经过访问这个地址来修改变量。
而引用只是一个别名,仍是变量自己。对引用进行的任何操做就是对变量自己进行操做,所以以达到修改变量的目的。
注:
(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。
而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。
(2)能够有const指针,可是没有const引用(const引用可读不可改,与绑定对象是否为const无关)
注:引用能够指向常量,也能够指向变量。例如int &a=b,使引用a指向变量b。而为了让引用指向常量,必须使用常量引用,如const int &a=1; 它表明的是引用a指向一个const int型,这个int型的值不能被改变,而不是引用a的指向不能被改变,由于引用的指向原本就是不可变的,无需加const声明。即指针存在常量指针int const *p和指针常量int *const p,而引用只存在常量引用int const &a,不存在引用常量int& const a。
(3)指针能够有多级,可是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(4)指针的值能够为空,可是引用的值不能为NULL,而且引用在定义的时候必须初始化;
(5)指针的值在初始化后能够改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(6)"sizeof引用"获得的是所指向的变量(对象)的大小,而"sizeof指针"获得的是指针自己的大小;
(7)指针和引用的自增(++)运算意义不同;
(8)指针使用时须要解引用(*),引用则不须要;
C++ 多态有两种:静态多态(早绑定)、动态多态(晚绑定)。静态多态是经过函数重载实现的;动态多态是经过虚函数实现的。
1.定义:“一个接口,多种方法”,程序在运行时才决定要调用的函数。
2.实现:C++多态性主要是经过虚函数实现的,虚函数容许子类重写override(注意和overload的区别,overload是重载,是容许同名函数的表现,这些函数参数列表/类型不一样)。
注:多态与非多态的实质区别就是函数地址是静态绑定仍是动态绑定。若是函数的调用在编译器编译期间就能够肯定函数的调用地址,并产生代码,说明地址是静态绑定的;若是函数调用的地址是 须要在运行期间才肯定,属于动态绑定。
3.目的:接口重用。封装可使得代码模块化,继承能够扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。
4.用法:声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,能够根据指向的子类的不一样而实现不一样的方法。
用一句话归纳:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。若是对象类型是派生类,就调用派生类的函数;若是对象类型是基类,就调用基类的函数。
关于重载、重写、隐藏的区别 Overload(重载):在C++程序中,能够将语义、功能类似的几个函数用同一个名字表示,但参数或返回值不一样(包括类型、顺序不一样),即函数重载。 (1)相同的范围(在同一个类中); (2)函数名字相同; (3)参数不一样; (4)virtual 关键字无关紧要。 Override(覆盖或重写):是指派生类函数覆盖基类函数,特征是: (1)不一样的范围(分别位于派生类与基类); (2)函数名字相同; (3)参数相同; (4)基类函数必须有virtual 关键字。 注:重写基类虚函数的时候,会自动转换这个函数为virtual函数,无论有没有加virtual,所以重写的时候不加virtual也是能够的,不过为了易读性,仍是加上比较好。 Overwrite(重写):隐藏,是指派生类的函数屏蔽了与其同名的基类函数,规则以下: (1)若是派生类的函数与基类的函数同名,可是参数不一样。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。 (2)若是派生类的函数与基类的函数同名,而且参数也相同,可是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
虚函数表:
详细解释能够参考博客:https://www.cnblogs.com/jin521/p/5602190.html
多态是由虚函数实现的,而虚函数主要是经过虚函数表(V-Table)来实现的。
若是一个类中包含虚函数(virtual修饰的函数),那么这个类就会包含一张虚函数表,虚函数表存储的每一项是一个虚函数的地址。以下图:
这个类的每个对象都会包含一个虚指针(虚指针存在于对象实例地址的最前面,保证虚函数表有最高的性能),这个虚指针指向虚函数表。
注:对象不包含虚函数表,只有虚指针,类才包含虚函数表,派生类会生成一个兼容基类的虚函数表。
下图是原始基类的对象,能够看到虚指针在地址的最前面,指向基类的虚函数表(假设基类定义了3个虚函数)
假设如今派生类继承基类,而且从新定义了3个虚函数,派生类会本身产生一个兼容基类虚函数表的属于本身的虚函数表。
Derive Class继承了Base Class中的3个虚函数,准确说是该函数的实体地址被拷贝到Derive Class的虚函数列表中,派生新增的虚函数置于虚函数列表后面,并按声明顺序摆放。
如今派生类重写基类的x函数,能够看到这个派生类构建本身的虚函数表的时候,修改了base::x()这一项,指向了本身的虚函数。
这个派生类多重继承了两个基类base1,base2,所以它有两个虚函数表。
它的对象会有多个虚指针(听说和编译器相关),指向不一样的虚函数表。
注:有关以上虚函数表等详见c++对象模型。连接地址:http://www.javashuo.com/article/p-wkwpgphp-gb.html
纯虚函数:
定义: 在不少状况下,基类自己生成对象是不合情理的。为了解决这个问题,方便使用类的多态性,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;)纯虚函数不能再在基类中实现,编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。称带有纯虚函数的类为抽象类。
特色:
1,当想在基类中抽象出一个方法,且该基类只作能被继承,而不能被实例化;(避免类被实例化且在编译时候被发现,能够采用此方法)
2,这个方法必须在派生类(derived class)中被实现;
目的:使派生类仅仅只是继承函数的接口。
size()指容器当前拥有的元素个数(对应的resize(size_type)会在容器尾添加或删除一些元素,来调整容器中实际的内容,使容器达到指定的大小。);capacity()指容器在必须分配存储空间以前能够存储的元素总数。
size表示的这个vector里容纳了多少个元素,capacity表示vector可以容纳多少元素,它们的不一样是在于vector的size是2倍增加的。若是vector的大小不够了,好比如今的capacity是4,插入到第五个元素的时候,发现不够了,此时会给他从新分配8个空间,把原来的数据及新的数据复制到这个新分配的空间里。(会有迭代器失效的问题)
详细参考:连接
底层数据结构:
根据应用场景进行选择:
VS下检测内存泄漏方法:
#define CRTDBG_MAP_ALLOC #include <stdlib.h> #include <crtdbg.h> //在入口函数中包含 _CrtDumpMemoryLeaks(); //便可检测到内存泄露 //以以下测试函数为例: int main() { char* pChars = new char[10]; //delete[]pChars; _CrtDumpMemoryLeaks(); system("pause"); return 0; }
解决方法:
/*若是不使用override,当你手一抖,将foo()写成了f00()会怎么样呢?结果是编译器并不会报错,由于它并不知道你的目的是重写虚函数,而是把它当成了新的函数。若是这个虚函数很重要的话,那就会对整个程序不利。 因此,override的做用就出来了,它指定了子类的这个虚函数是重写的父类的,若是你名字不当心打错了的话,编译器是不会编译经过的:*/ class A { virtual void foo(); } class B :public A { void foo(); //OK virtual foo(); // OK void foo() override; //OK } class A { virtual void foo(); }; class B :A { virtual void f00(); //OK virtual void f0o()override; //Error };
/*当不但愿某个类被继承,或不但愿某个虚函数被重写,能够在类名和虚函数后添加final关键字,添加final关键字后被继承或重写,编译器会报错。例子以下:*/ class Base { virtual void foo(); }; class A : Base { void foo() final; // foo 被override而且是最后一个override,在其子类中不能够重写 void bar() final; // Error: 父类中没有 bar虚函数能够被重写或final }; class B final : A // 指明B是不能够被继承的 { void foo() override; // Error: 在A中已经被final了 }; class C : B // Error: B is final { };
C++在C的基础上增添类,C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何经过一个过程,对输入(或环境条件)进行运算处理获得输出(或实现过程(事务)控制),而对于C++,首要考虑的是如何构造一个对象模型,让这个模型可以契合与之对应的问题域,这样就能够经过获取对象的状态信息获得输出或实现过程(事务)控制。
1.编译器处理方式
define – 在预处理阶段进行替换
const – 在编译时肯定其值
2.类型检查
define – 无类型,不进行类型安全检查,可能会产生意想不到的错误
const – 有数据类型,编译时会进行类型检查
3.内存空间
define – 不分配内存,给出的是当即数,有多少次使用就进行多少次替换,在内存中会有多个拷贝,消耗内存大
const – 在静态存储区中分配空间,在程序运行过程当中内存中只有一个拷贝
4.其余
在编译时, 编译器一般不为const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操做,使得它的效率也很高。
宏替换只做替换,不作计算,不作表达式求解。
{ char *dp = NULL; { char c; dp = &c; } //变量c释放,dp变成空悬指针 }
void func() { char *dp = (char *)malloc(A_CONST); free(dp); //dp变成一个空悬指针 dp = NULL; //dp再也不是空悬指针 /* ... */ }
int func() { char *dp;//野指针,没有初始化 static char *sdp;//非野指针,由于静态变量会默认初始化为0 }
本质区别是访问的默认控制:默认的继承访问权限,class是private,struct是public;
功能不一样:
sizeof是操做符,参数为任意类型,主要计算类型占用内存大小。
strlen()是函数,其函数原型为:extern unsigned int strlen(char *s);其参数为char*,strlen只能计算以"\0"结尾字符串的长度,计算结果不包括"\0"。
char* ss="0123456789"; //s1=4,ss为字符指针在内存中占用4个字节 int s1=sizeof(ss); //s2=10,计算字符串ss的长度 int s2=strlen(ss);
参数不一样:
当将字符数组做为sizeof()的参数时,计算字符数组占用内存大小;当将字符数组做为strlen()函数,字符数组转化为char*。由于sizeof的参数为任意类型,而strlen()函数参数只能为char*,当参数不是char*必须转换为char*。
char str[]="abced"; //a为6(1*6),字符数组str包含6个元素(a,b,c,d,e,\0),每一个元素占用1个字节 int a= sizeof(str); //len为5,不包含"\0", int len=strlen(str); //str[0]是字符元素a,因此b=1 int b= sizeof(str[0]);
char :1个字节(固定)
*(即指针变量): 4个字节(32位机的寻址空间是4个字节。同理64位编译器)(变化*)
short int : 2个字节(固定)
int: 4个字节(固定)
unsigned int : 4个字节(固定)
float: 4个字节(固定)
double: 8个字节(固定)
long: 4个字节
unsigned long: 4个字节(变化*,其实就是寻址控件的地址长度数值)
long long: 8个字节(固定)
64位操做系统
char :1个字节(固定)
*(即指针变量): 8个字节
short int : 2个字节(固定)
int: 4个字节(固定)
unsigned int : 4个字节(固定)
float: 4个字节(固定)
double: 8个字节(固定)
long: 8个字节
unsigned long: 8个字节(变化*其实就是寻址控件的地址长度数值)
long long: 8个字节(固定)
除*与long 不一样其他均相同。
inline:在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数。
#include <stdio.h> //函数定义为inline即:内联函数 inline char* dbtest(int a) { return (i % 2 > 0) ? "奇" : "偶"; } int main() { int i = 0; for (i=1; i < 100; i++) { printf("i:%d 奇偶性:%s /n", i, dbtest(i)); } }//在for循环的每一个dbtest(i)的地方替换成了 (i % 2 > 0) ? "奇" : "偶",避免了频繁调用函数,对栈内存的消耗
decltype:从表达式中推断出要定义变量的类型,但却不想用表达式的值去初始化变量。还有多是函数的返回类型为某表达式的的值类型。
volatile:volatile 关键字是一种类型修饰符,用它声明的类型变量表示能够被某些编译器未知的因素更改,好比:操做系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就再也不进行优化,从而能够提供对特殊地址的稳定访问。
static:
在变量和函数名前面若是未加static,则它们是全局可见的。加了static,就会对其它源文件隐藏,利用这一特性能够在不一样的文件中定义同名函数和同名变量,而没必要担忧命名冲 突。static能够用做函数和变量的前缀,对于函数来说,static的做用仅限于隐藏 。
2.static变量中的记忆功能和全局生存期
存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是惟一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static能够控制变量的可见范围,说到底static仍是用来隐藏的。PS:若是做为static局部变量在函数内定义,它的生存期为整个源程序,可是其做用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。
#include <stdio.h> int fun(){ static int count = 10; //在第一次进入这个函数的时候,变量a被初始化为10!并接着自减1,之后每次进入该函数,a return count--; //就不会被再次初始化了,仅进行自减1的操做;在static发明前,要达到一样的功能,则只能使用全局变量: } int count = 1; int main(void) { printf("global\t\tlocal static\n"); for(; count <= 10; ++count) printf("%d\t\t%d\n", count, fun()); return 0; }
---基于以上两点能够得出一个结论:把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的做用域, 限制了它的使用范围。所以static 这个说明符在不一样的地方所起的做用是不一样的。
3.static的第三个做用是默认初始化为0(static变量)
最后对static的三条做用作一句话总结。首先static的最主要功能是隐藏,其次由于static变量存放在静态存储区,因此它具有持久性和默认值0。
4.static的第四个做用:C++中的类成员声明static(有些地方与以上做用重叠)
在类中声明static变量或者函数时,初始化时使用做用域运算符来标明它所属类,所以,静态数据成员是类的成员,而不是对象的成员,这样就出现如下做用:
(1)类的静态成员函数是属于整个类而非类的对象,因此它没有this指针,这就致使 了它仅能访问类的静态数据和静态成员函数。
(2)不能将静态成员函数定义为虚函数。
(3)因为静态成员声明于类中,操做于其外,因此对其取地址操做,就多少有些特殊 ,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。
(4)因为静态成员函数没有this指针,因此就差很少等同于nonmember函数,结果就 产生了一个意想不到的好处:成为一个callback函数,使得咱们得以将C++和C-based X W indow系统结合,同时也成功的应用于线程函数身上。 (这条没碰见过)
(5)static并无增长程序的时空开销,相反她还缩短了子类对父类静态成员的访问 时间,节省了子类的内存空间。
(6)静态数据成员在<定义或说明>时前面加关键字static。
(7)静态数据成员是静态存储的,因此必须对它进行初始化。 (程序员手动初始化,不然编译时通常不会报错,可是在Link时会报错误)
(8)静态成员初始化与通常数据成员初始化不一样:
初始化在类体外进行,而前面不加static,以避免与通常静态变量或对象相混淆;
初始化时不加该成员的访问权限控制符private,public等;
初始化时使用做用域运算符来标明它所属类;
因此咱们得出静态数据成员初始化的格式:
<数据类型><类名>::<静态数据成员名>=<值>
(9)为了防止父类的影响,能够在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点须要注意:咱们说静态成员为父类和子类共享,但咱们有重复定义了静态成员,这会不会引发错误呢?不会,咱们的编译器采用了一种绝妙的手法:name-mangling 用以生成惟一的标志。
1.何时用到拷贝函数?
若是在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝;
2.是否应该自定义拷贝函数?
自定义拷贝构造函数是一种良好的编程风格,它能够阻止编译器造成默认的拷贝构造函数,提升源码效率。
3.什么叫深拷贝?什么是浅拷贝?二者异同?
4.深拷贝好仍是浅拷贝好?
若是实行位拷贝,也就是把对象里的值彻底复制给另外一个对象,如A=B。这时,若是B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
参考博客:https://blog.csdn.net/caoshangpa/article/details/79226270
http://www.cnblogs.com/BlueTzar/articles/1223313.html
构造函数:“先基后派”;析构函数:“先派后基”。
1.成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。
2.若是不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。
3.类中const成员常量必须在构造函数初始化列表中初始化。
4.类中static成员变量,只能在类内外初始化(同一类的全部实例共享静态成员变量)。
初始化顺序:
注:内存对齐是看类型,而不是看总的字节数。好比:
#include<iostream> using namespace std; struct AlignData1 { int a; char b[7];//a后面并不会补上3个字节,而是因为char的类型因此不用补。 short c; char d; }Node; struct AlignData2 { bool a; int b[2];//a后面并不会补上7个字节,而是根据int的类型补3个字节。 int c; int d; }Node2; int main(){ cout << sizeof(Node) << endl;//16 cout << sizeof(Node2) << endl;//20 system("pause"); return 0; }
补充:
struct AlignData1 { char c; short b; int i; char d; }Node; 这个结构体在编译之后,为了字节对齐,会被整理成这个样子: struct AlignData1 { char c; char padding[1]; short b; int i; char d; char padding[3]; }Node;
含有虚函数的类的大小:连接
补充:联合体的大小计算:
联合体所占的空间不只取决于最宽成员,还跟全部成员有关系,即其大小必须知足两个条件:1)大小足够容纳最宽的成员;2)大小能被其包含的全部基本数据类型的大小所整除。
union U1 { int n; char s[11]; double d; }; //16,char s[11]按照char=1能够整除 union U2 { int n; char s[5]; double d; }; //8
补充:static_cast与dynamic_cast
#include <iostream> using namespace std; class CBasic { public: virtual int test(){return 0;} }; class CDerived : public CBasic { public: virtual int test(){ return 1;} }; int main() { CBasic cBasic; CDerived cDerived; CBasic * pB1 = new CBasic; CBasic * pB2 = new CDerived; CBasic * pB3 = new CBasic; CBasic * pB4 = new CDerived; //dynamic cast failed, so pD1 is null. CDerived * pD1 = dynamic_cast<CDerived * > (pB1); //dynamic cast succeeded, so pD2 points to CDerived object CDerived * pD2 = dynamic_cast<CDerived * > (pB2); //pD3将是一个指向该CBasic类型对象的指针,对它进行CDerive类型的操做将是不安全的 CDerived * pD3 = static_cast<CDerived * > (pB3); //static_cast成功 CDerived * pD4 = static_cast<CDerived * > (pB4); //dynamci cast failed, so throw an exception. // CDerived & rD1 = dynamic_cast<CDerived &> (*pB1); //dynamic cast succeeded, so rD2 references to CDerived object. CDerived & rD2 = dynamic_cast<CDerived &> (*pB2); return 0; }
注:CBasic要有虚函数,不然会编译出错;static_cast则没有这个限制。
class A {};: sizeof(A) = 1;
class A { virtual Fun(){} };: sizeof(A) = 4(32位机器)/8(64位机器);
class A { static int a; };: sizeof(A) = 1;
class A { int a; };: sizeof(A) = 4;
class A { static int a; int b; };: sizeof(A) = 4;
类中用static声明的成员变量不计算入类的大小中,由于static data不是实例的一部分。static的属于全局的,他不会占用类的存储,他有专门的地方存储 (全局变量区)
各自优点:
举一个例子,好比数字0x12 34 56 78在内存中的表示形式为:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
template <class T>
void InsertFront(Node<T>* & head, T item)
上面一个函数的声明,其中第一个参数*和&分别是什么意思?
head是个指针,前面为何加个&
原本“* head”表明的是传指针的,可是只能改变head指向的内容,而“* &head”意思是说head是传进来的指针的同名指针,就能既改变*head指向的内容,又能改变head这个指针。好比:main()有个Node<int>* p,int t;当调用insertFront(p,t)是,若是template <class T> void InsertFront(Node<T>* & head, T item)中有对head进行赋值改变时,main()中的p也会跟着改变,若是没有&这个别名标识时,p则不会随着head的改变而改变。
http://www.javashuo.com/article/p-qvvgctsh-eg.html
在C语言中static的做用以下
第1、在修饰变量的时候,static修饰的静态局部变量只执行一次,并且延长了局部变量的生命周期,直到程序运行结束之后才释放。
第2、static修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即使是extern外部声明也不能够。
第3、static修饰一个函数,则这个函数的只能在本文件中调用,不能被其余文件调用。Static修饰的局部变量存放在全局数据区的静态变量区。初始化的时候自动初始化为0;
(1)不想被释放的时候,可使用static修饰。好比修饰函数中存放在栈空间的数组。若是不想让这个数组在函数调用结束释放可使用static修饰
(2)考虑到数据安全性(当程想要使用全局变量的时候应该先考虑使用static)
在C++中static关键字除了具备C中的做用还有在类中的使用
在类中,static能够用来修饰静态数据成员和静态成员方法
静态数据成员
(1)静态数据成员能够实现多个对象之间的数据共享,它是类的全部对象的共享成员,它在内存中只占一份空间,若是改变它的值,则各对象中这个数据成员的值都被改变。
(2)静态数据成员是在程序开始运行时被分配空间,到程序结束以后才释放,只要类中指定了静态数据成员,即便不定义对象,也会为静态数据成员分配空间。
(3)静态数据成员能够被初始化,可是只能在类体外进行初始化,若为对静态数据成员赋初值,则编译器会自动为其初始化为0
(4)静态数据成员既能够经过对象名引用,也能够经过类名引用。
静态成员函数
(1)静态成员函数和静态数据成员同样,他们都属于类的静态成员,而不是对象成员。
(2)非静态成员函数有this指针,而静态成员函数没有this指针。
(3)静态成员函数主要用来访问静态数据成员而不能访问非静态成员。
讲解全面的一篇博客:http://www.javashuo.com/article/p-kqoifllz-hp.html
若是你只是声明一个空类,不作任何事情的话,编译器会自动为你生成一个默认构造函数、一个拷贝默认构造函数、一个默认拷贝赋值操做符和一个默认析构函数。这些函数只有在第一次被调用时,才会被编译器建立。全部这些函数都是inline和public的。
定义一个空类例如:
class Empty { }
一个空的class在C++编译器处理事后就再也不为空,编译器会自动地为咱们声明一些member function,通常编译过就至关于:
class Empty { public: Empty(); // 缺省构造函数// Empty( const Empty& ); // 拷贝构造函数// ~Empty(); // 析构函数// Empty& operator=( const Empty& ); // 赋值运算符// };
须要注意的是,只有当你须要用到这些函数的时候,编译器才会去定义它们。
基类指针能够指向派生类的对象(多态性),若是删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象彻底被释放。
若是析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会形成派生类对象析构不彻底。
不能被继承的函数和不能被重写的函数。
1)普通函数
普通函数不属于成员函数,是不能被继承的。普通函数只能被重载,不能被重写,所以声明为虚函数没有意义。由于编译器会在编译时绑定函数。
而多态体如今运行时绑定。一般经过基类指针指向子类对象实现多态。
2)友元函数
友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。
3)构造函数
首先说下什么是构造函数,构造函数是用来初始化对象的。假如子类能够继承基类构造函数,那么子类对象的构造将使用基类的构造函数,而基类构造函数并不知道子类的有什么成员,显然是不符合语义的。从另一个角度来说,多态是经过基类指针指向子类对象来实现多态的,在对象构造以前并无对象产生,所以没法使用多态特性,这是矛盾的。所以构造函数不容许继承。
4)内联成员函数
咱们须要知道内联函数就是为了在代码中直接展开,减小函数调用花费的代价。也就是说内联函数是在编译时展开的。而虚函数是为了实现多态,是在运行时绑定的。所以显然内联函数和多态的特性相违背。
5)静态成员函数
首先静态成员函数理论是可继承的。可是静态成员函数是编译时肯定的,没法动态绑定,不支持多态,所以不能被重写,也就不能被声明为虚函数。、
//.h
class String{ public: String(const char* str); String(const String &other); ~String(); String & operate=(const String &other); private: char* m_data; };
//.cpp String::String(const char*str){ if(str==NULL){ m_data=new char[1]; *m_data='\0'; } else{ int length=strlen(str); m_data=new char[length+1]; strcpy(m_data,str); } } String::String(const String &other){ int length=strlen(other.m_data); m_data=new char[length+1]; strcpy(m_data,other.m_data); } String::~String(){ delete [] m_data; } String::String& operate=(const String & other){ if(&other==*this)return *this;//检查自赋值 delete[]m_data;//释放原有的内存资源 int length=strlen(other.m_data); m_data=new char[length+1]; strcpy(m_data,other.m_data); return *this;//返回本对象的引用 }
参考:http://www.javashuo.com/article/p-mgvopjua-hu.html
注:1.引用计数问题
参考:http://www.javashuo.com/article/p-xixpwpyp-ck.html
2.智能指针支持的操做
#include <iostream> #include <string> #include <memory> using namespace std; class base { public: base(int _a): a(_a) {cout<<"构造函数"<<endl;} ~base() {cout<<"析构函数"<<endl;} int a; }; int main() { unique_ptr<base> up1(new base(2)); // unique_ptr<base> up2 = up1; //编译器提示未定义 unique_ptr<base> up2 = move(up1); //转移对象的全部权 // cout<<up1->a<<endl; //运行时错误 cout<<up2->a<<endl; //经过解引用运算符获取封装的原始指针 up2.reset(); // 显式释放内存 shared_ptr<base> sp1(new base(3)); shared_ptr<base> sp2 = sp1; //增长引用计数 cout<<"共享智能指针的数量:"<<sp2.use_count()<<endl; //2 sp1.reset(); // cout<<"共享智能指针的数量:"<<sp2.use_count()<<endl; //1 cout<<sp2->a<<endl; auto sp3 = make_shared<base>(4);//利用make_shared函数动态分配内存 }
3.智能指针的陷阱(循环引用等问题)
class B; class A { public: shared_ptr<B> m_b; }; class B { public: shared_ptr<A> m_a; }; int main() { { shared_ptr<A> a(new A); //new出来的A的引用计数此时为1 shared_ptr<B> b(new B); //new出来的B的引用计数此时为1 a->m_b = b; //B的引用计数增长为2 b->m_a = a; //A的引用计数增长为2 } //b先出做用域,B的引用计数减小为1,不为0; //因此堆上的B空间没有被释放,且B持有的A也没有机会被析构,A的引用计数也彻底没减小 //a后出做用域,同理A的引用计数减小为1,不为0,因此堆上A的空间也没有被释放 }
循环引用”简单来讲就是:两个对象互相使用一个shared_ptr成员变量指向对方会形成循环引用。
即A内部有指向B,B内部有指向A,这样对于A,B一定是在A析构后B才析构,对于B,A一定是在B析构后才析构A,这就是循环引用问题,违反常规,致使内存泄露。
解决循环引用方法:
1. 当只剩下最后一个引用的时候须要手动打破循环引用释放对象。
2. 当A的生存期超过B的生存期的时候,B改成使用一个普通指针指向A。
3. 使用weak_ptr打破这种循环引用,由于weak_ptr不会修改计数器的大小,因此就不会产生两个对象互相使用一个shared_ptr成员变量指向对方的问题,从而不会引发引用循环。
参考连接:https://blog.csdn.net/yangshiziping/article/details/52550291
1.宏定义不是函数,可是使用起来像函数。预处理器用复制宏代码的方式代替函数的调用,省去了函数压栈退栈过程,提升了效率。
内联函数本质上是一个函数,内联函数通常用于函数体的代码比较简单的函数,不能包含复杂的控制语句,while、switch,而且内联函数自己不能直接调用自身。若是内联函数的函数体过大,编译器会自动 的把这个内联函数变成普通函数。
2. 宏定义是在预处理的时候把全部的宏名用宏体来替换,简单的说就是字符串替换
内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样能够省去函数的调用的开销,提升效率
3. 宏定义是没有类型检查的,不管对仍是错都是直接替换
内联函数在编译的时候会进行类型的检查,内联函数知足函数的性质,好比有返回值、参数列表等
4. 宏定义和内联函数使用的时候都是进行代码展开。不一样的是宏定义是在预编译的时候把全部的宏名替换,内联函数则是在编译阶段把全部调用内联函数的地方把内联函数插入。这样能够省去函数压栈退栈,提升了效率
1. 内联函数和普通函数的参数传递机制相同,可是编译器会在每处调用内联函数的地方将内联函数内容展开,这样既避免了函数调用的开销又没有宏机制的缺陷。
2. 普通函数在被调用的时候,系统首先要到函数的入口地址去执行函数体,执行完成以后再回到函数调用的地方继续执行,函数始终只有一个复制。
内联函数不须要寻址,当执行到内联函数的时候,将此函数展开,若是程序中有N次调用了内联函数则会有N次展开函数代码。
3. 内联函数有必定的限制,内联函数体要求代码简单,不能包含复杂的结构控制语句。若是内联函数函数体过于复杂,编译器将自动把内联函数当成普通函数来执行。
不能。
C++编译器在实现const的成员函数(const加在函数右边)的时候为了确保该函数不能修改类的中参数的值,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。
即:static修饰的函数表示该函数是属于类的,而不是属于某一个对象的,没有this指针。const修饰的函数表示该函数不能改变this中的内容,会有一个隐含的const this指针。二者是矛盾的。
1.溢出
要求分配的内存超出了系统能给你的,系统不能知足需求,因而产生溢出。
1)栈溢出
a.栈溢出是指函数中的局部变量形成的溢出(注:函数中形参和函数中的局部变量存放在栈上)
栈的大小一般是1M-2M,因此栈溢出包含两种状况,一是分配的的大小超过栈的最大值,二是分配的大小没有超过最大值,可是接收的buff比新buff小(buff:缓冲区, 它本质上就是一段存储数据的内存)
例子1:(分配的的大小超过栈的最大值)
void { char a[99999999999999999]; }
例子2:(接收的buff比新buff小)
void { char a[10] = {0}; strcpy(a, "abjjijjlljiojohihiihiiiiiiiiiiiiiiiiiiiiiiiiii"); }
注意:调试时栈溢出的异常要在函数调用结束后才会检测到,由于栈是在函数结束时才会开始进行出栈操做
如:
int main(int argc, char* argv[]) { char a[10] = {0}; strcpy(a, "abjjijjlljiojohihiihiiiiiiiiiiiiiiiiiiiiiiiiii"); exit(0); return 0; }
上面状况是检测不到栈溢出的,由于函数还没执行完就退出了
void fun() { char a[10] = {0}; strcpy(a, "abjjijjlljiojohihiihiiiiiiiiiiiiiiiiiiiiiiiiii"); } int main(int argc, char* argv[]) { fun(); exit(0); return 0; }
这种状况调用完fun函数就会检测到异常了
b.栈溢出的解决办法
若是是超过栈的大小时,那就直接换成用堆;若是是不超过栈大小可是分配值小的,就增大分配的大小
2)内存溢出
使用malloc和new分配的内存,在拷贝时接收buff小于新buff时形成的现象
解决:增长分配的大小
2.越界
越界一般指的是数组越界,如
char a[9]={0};
cout << a[9] << endl;
3.泄漏
这里泄漏一般是指堆内存泄漏,是指使用malloc和new分配的内存没有释放形成的
1) malloc 函数: void *malloc(unsigned int size)
在内存的动态分配区域中分配一个长度为size的连续空间,若是分配成功,则返回所分配内存空间的首地址,不然返回NULL,申请的内存不会进行初始化。
2)calloc 函数: void *calloc(unsigned int num, unsigned int size)
按照所给的数据个数和数据类型所占字节数,分配一个 num * size 连续的空间。
动态分配一个长度为size的内存空间,并把内存空间的首地址赋值给ptr,把ptr内存空间调整为size。
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每一个数据成员后面跟一个放在括号中的初始化式。例如:
class CExample { public: int a; float b; //构造函数初始化列表 CExample(): a(0),b(8.8) {} //构造函数内部赋值 CExample() { a=0; b=8.8; } };
上面的例子中两个构造函数的结果是同样的。上面的构造函数(使用初始化列表的构造函数)显式的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并无进行显式的初始化。
初始化和赋值对内置类型的成员没有什么大的区别,像上面的任一个构造函数均可以。对非内置类型成员变量,为了不两次构造,推荐使用类构造函数初始化列表。但有的时候必须用带有初始化列表的构造函数:
1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
2.const成员或引用类型的成员。由于const对象或引用类型只能初始化,不能对他们赋值。
初始化数据成员与对数据成员赋值的含义是什么?有什么区别?
首先把数据成员按类型分类并分状况说明:
1.内置数据类型,复合类型(指针,引用)
在成员初始化列表和构造函数体内进行,在性能和结果上都是同样的
2.用户定义类型(类类型)
结果上相同,可是性能上存在很大的差异。由于类类型的数据成员对象在进入函数体前已经构造完成(先进行了一次隐式的默认构造函数调用),也就是说在成员初始化列表处进行构造对象的工做,调用构造函数,在进入函数体以后,进行的是对已经构造好的类对象的赋值,又调用了拷贝赋值操做符才能完成(若是并未提供,则使用编译器提供的默认按成员赋值行为)。
void f(vector<int> &v) { v[5]; // A v.at[5]; // B }
若是v非空,A行和B行没有任何区别。若是v为空,B行会抛出std::out_of_range异常,A行的行为未定义。
c++标准不要求vector<T>::operator[]进行下标越界检查,缘由是为了效率,老是强制下标越界检查会增长程序的性能开销。设计vector是用来代替内置数组的,因此效率问题也应该考虑。不过使用operator[]就要本身承担越界风险了。
若是须要下标越界检查,请使用at。可是请注意,这时候的性能也是响应的会受影响,由于越界检查增长了性能的开销。
extern "C"
常量指针(被指向的对象是常量)
定义:又叫常指针,能够理解为常量的指针,指向的是个常量
关键点:
const int *p或int const *p
(记忆技巧:const读做常量,*读做指针)
#include <stdio.h> // 常量指针(被指向的对象是常量) int main() { int i = 10; int i2 = 11; const int *p = &i; printf("%d\n", *p);//10 i = 9; //OK,仍然能够经过原来的声明修改值, //Error,*p是const int的,不可修改,即常量指针不可修改其指向地址 //*p = 11; //error: assignment of read-only location ‘*p’ p = &i2;//OK,指针还能够指向别处,由于指针只是个变量,能够随意指向; printf("%d\n", *p);//11 return 0; }
指针常量(指针自己是常量)
定义:
本质是一个常量,而用指针修饰它。指针常量的值是指针,这个值由于是常量,因此不能被赋值。
关键点:
int* const p;
//指针常量(指针自己是常量) #include <stdio.h> int main() { int i = 10; int *const p = &i; printf("%d\n", *p);//10 //Error,由于p是const 指针,所以不能改变p指向的内容 //p++;//error: increment of read-only variable ‘p’ (*p)++; //OK,指针是常量,指向的地址不能够变化,可是指向的地址所对应的内容能够变化 printf("%d\n", *p);//11 i = 9;//OK,仍然能够经过原来的声明修改值, return 0; }
很是好的一篇博客:连接