一、虚函数
虚函数一般是定义在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,用法格式为:virtual 函数返回类型 函数名(){}。虚函数的作用就是用于运行多态性,即在类继承中,基类和派生类会有相同的成员函数,指向基类的指针在操作它的多态类对象时(这个指针可以指向基类的对象,也可以指向派生类的对象),会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。一般的成员函数(包括虚函数)可以只有声明,前提是在应用中不能调用该函数,否则会因找不到定义产生连接错误!
class A{
public:
void function(){}
}
class B : public A{
public:
void function(){}
}
int main(){
A a;
B b;
A *p1 = &a;
A *p2 = &b;
p1->function();
p2->function();
}
在上面的代码中我们定义了基类A和派生类B,若function函数不是虚函数时,则输出时p1和p2调用的都是基类A的函数function,因为它们都是A类型的指针,若是在基类A的function函数前面添加virtual使之成为虚函数,则输出时p1和p2调用的函数就分别是A和B的function函数了。这就是虚函数的作用,即在基类指针指向多态对象时,根据对象所属的类来调用相应的函数。
虚函数使用事项:
1. 在基类用virtual声明成员函数为虚函数。 这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,不必再加virtual。
2. 在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。
3. 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。通过虚函数与指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类的同名函数,只要先用基类指针指向即可。如果指针不断地指向同一类族中不同类的对象,就能不断地调用这些对象中的同名函数。
此节部分参考内容及详细实例可以参考《什么是C++虚函数、虚函数的作用和使用方法》。
C++的多态性更多内容参考[C++之多态性与虚函数](http://www.cnblogs.com
只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数。
静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。
内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。即使虚函数在类的内部定义,但是在编译的时候系统仍然将它看做是非内联的。
构造函数不能是虚函数,因为构造的时候,对象还是一片位定型的空间,只有构造完成后,对象才是具体类的实例。析构函数可以是虚函数,而且通常声名为虚函数。
纯虚函数:即基类中没有给出有意义的实现的虚函数,类似于Java中的接口函数。纯虚函数一般都直接=0,表明他的直接功能由子类进行定义,他是虚函数的其中一种。派生类中必须要实现纯虚函数。
纯虚类:只要有一个纯虚函数,就称为纯虚类也叫抽象类,纯虚类不可以定义一个对象。
Java有关虚函数等于C++的区别:在Java中一般没有虚函数的概念,Java继承中最常见的概念就是抽象类和接口,关于两者区别联系参考:JAVA – 虚函数、抽象函数、抽象类、接口
二、虚析构函数
C++中与构造函数对应的就是析构函数了,我们都析构函数的作用就是用于释放内存清除不需要的数据空间。一般是在构造函数前加一个~就是析构函数了,这里要讲的虚析构函数就是在析构函数前再加上virtual。虚析构函数的作用就是:为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。一般来讲也只有当一个类用作基类时才把析构函数写成虚析构函数。其实这就是虚函数在析构函数上的一种应用,详细的讲解可参考C++中虚析构函数的作用 。
一般来说,带多态性质的基类应该声明一个virtual析构函数,一个类如果不是用于基类或是多态的化,不应该声明virtual析构函数。常见的string,STL都不被设计为基类使用,即在这些类中,不存在virtual析构函数,最好不要用来当作基类进行派生。
三、#program once
在有些代码中可以看到这一句:#program once,这个意思就是当前文件不会被包含多次,当前文件应用上指的是物理上的一个文件,它的作用于最常见的#ifndef #define #endif作用是相似的。具体区别参考:#ifndef 与 #program once 的区别 。
四、typedef #define
typedef和#define都可以用来定义别名,但两者的顺序刚好相反,如把INT定义成C++的数据类型int,即INT为int的别名,则两种写法分别为:
typedef int INT;
#define INT int;
这里的typedef定义的只是一个别名,而#define定义的是一个宏,虽然也有别名的一个功能。
作用域:
typedef:
如果放在所有函数之外,它的作用域就是从它定义开始直到文件尾;
如果放在某个函数内,定义域就是从定义开始直到该函数结尾;
#define:
不管是在某个函数内,还是在所有函数之外,作用域都是从定义开始直到整个文件结尾。不管是typedef还是define,其作用域都不会扩展到别的文件,即使是同一个程序的不同文件,也不能互相使用。
typedef和#define的区别。
五、类定义相关函数
类定义中经常要定义构造函数和成员函数,但是有时需要注意,不然很容易出错。
为了便于管理,最后在类头文件中进行声明,然后在类.cpp文件中定义。这样最不容易出错。在.cpp中定义每一个函数前都要加上类名::这个作用域符号。在头文件声明类成员的时候不能给变量赋值。
对于构造函数和析构函数,要是有声明,就一定要定义实现,即使只是在后面加上{},也是可以的,不然没有定义会出现错误。
可以经常看到一些类成员函数在声明的时候就直接定义了,如下面的getX():
class C
{
public:
//C(){};
int getX(){return x;}
private:
int x;
};
/*不要在下面定义 int C::getX() { return x; }*/
函数直接在声明时就定义(在类声明中定义)的将被自动当作inline函数。一般函数都是在相应的.cpp文件中定义,但是注意最好不要在类外面定义,即在类声明完后在类外面同一个头文件中定义,有时是没有问题的,但是当这个文件被包含时,经常会发生重复定义的错误。因此定义个函数时最好按照标准的方式来进行。总结如下:
(1)成员函数定义放在类里面的是被自动当做inline
(2)如果类函数定义在类的外面但在.h文件里就必须要显式声明inline,否则当这个头文件被多个文件包含时就会被认为重复定义
(3)如果类成员函数在.cpp文件里面定义就不用加了,你想加也可以,但不是必须的。
参考:http://bbs.chinaunix.net/thread-1571848-1-1.html
六、inline函数
内联函数,C++中在函数返回类型前面添加inline用于标识这个函数为内联函数。在编译过程中内联函数会直接被源代码替换,提高执行效率 如果类中的某个函数会被调用很多次或者放在循环中,那么建议将这个函数声明为内联,可以提高程序的运行效率。它类似于宏定义的函数块,但相对于宏又有更好的优点。使用inline函数注意下面事项:
七、explicit
explicit是C++的一个关键字,用于阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。
class Test1
{
public:
Test1(int n)
{
num=n;
}//普通构造函数
private:
int num;
};
class Test2
{
public:
explicit Test2(int n)
{
num=n;
}//explicit(显式)构造函数
private:
int num;
};
int main()
{
Test1 t1=12;//隐式调用其构造函数,成功
Test2 t2=12;//编译错误,不能隐式调用其构造函数
Test2 t2(12);//显式调用成功
return 0;
}
Test1的构造函数带一个int型的参数,代码23行会隐式转换成调用Test1的这个构造函数。而Test2的构造函数被声明为explicit(显式),这表示不能通过隐式转换来调用这个构造函数,因此代码 Test2 t2=12会出现编译错误。普通构造函数能够被隐式调用,而explicit构造函数只能被显式调用。
参考文章explicit
八、heap(堆)和stack(栈)
1.heap是堆,stack是栈。
2.stack的空间由操作系统自动分配和释放,heap的空间是手动申请和释放的,heap常用new关键字或malloc()函数来分配,需要手动释放。
3.stack空间有限,heap的空间是很大的自由区。
详细参考:堆(heap)和栈(stack)有什么区别??
九、C和C++结构体的区别
1. C中的结构体只能定义数据变量,不允许有函数,只是涉及数据结构。
2. C++中的结构体不仅能定义数据变量,还能定义成员函数,可以被继承可以有虚函数,和类相似可以有构造函数等。
3. C中的数据只是公有的,而C++中的可以是公有、私有、保护等。
4. struct和typedef struct两种定义方式在C和C++中表示的意义不同。
在C中:
struct Test{
int a;
}; 表示结构体Test,创建一个结构体方式:struct Test Ex;
typedef struct Test{
int a;
}Te; 表示结构体Test,创建一个结构体方式:struct Test Ex或Te Ex;
在C++中:
struct Test{
int a;
}; 表示结构体Test,创建一个结构体方式:Test Ex;
struct Test{
int a;
}Te; 表示结构体Test,Te是一个结构体变量;
typedef struct Test{
int a;
}Te; 表示结构体Test,Te是一个结构体类型;
总结:typedef struct 在C和C++中区别不大,都是定义类型,主要区别在于struct。
十、C++中int数据
int在C++中是一个常见的数据类型,一般为4个字节,32位有符号的数据,大小范围为-2^31~2^31-1;最大值和最小值有宏定义INT_MAX, INT_MIN。
long 一般在32位系统中是和int一样的,64位系统是8字节,long long 为8字节
unsigned int 0~4294967295
int -2147483648~2147483647
unsigned long 0~4294967295
long -2147483648~2147483647
long long的最大值:9223372036854775807
long long的最小值:-9223372036854775808
unsigned long long的最大值:1844674407370955161
__int64的最大值:9223372036854775807
__int64的最小值:-9223372036854775808
unsigned __int64的最大值:18446744073709551615
C++中做运算,若是超过范围则会溢出(会有结果但不正确),因此要注意。其次需要注意的是对于最小的负值(如-2147483648)它的绝对值等于其本身,而不是2147483648,因为2147483648已经溢出了,参考代码之谜。
十一、C++构造函数
构造函数用来初始化一个对象,当没有自己定义构造函数时,编译器将自动为你生成一个默认的无参构造函数,如果自己定义了,编译器将不会提供默认构造函数。构造函数、析构函数、赋值操作符,这些都不能被继承。另外对于基类的静态成员,无论有多少个派生类,都只有这一个静态成员实例。 因此在派生类中一般都要有自己的构造函数吗,派生类中构造函数的调用顺为先调用基类的构造函数再调用派生类的构造函数,析构函数相反。
通过派生类创建对象时必须要调用基类的构造函数,这是语法规定。也就是说,定义派生类构造函数时最好指明基类构造函数;如果不指明,就调用基类的默认构造函数(不带参数的构造函数);如果没有默认构造函数,那么编译失败。
当我们在初始化一个子类对象的时候,而这个子类对象的父类有一个显示的带有参数的构造函数,需要使用成员初值化。
如果没有声明,则编译器会自动声明下面几个函数:default构造函数,copy构造函数,copy assignment构造函数,析构函数,当你定义了构造函数,他就不在提供,或当你定义了copy函数,他就不在提供默认的copy构造函数。
十二、C++成员初始化列表
class A{
public:
A(int b){num = b;}
A(int b):num(b){};//成员初始化列表
int num;
};
成员初始化列表和构造函数的函数体都可以为我们的类数据成员指定一些初值,但是两者在给成员指定初值的方式上是不同的。成员初始化列表使用初始化的方式来为数据成员指定初值,而构造函数的函数体是通过赋值的方式来给数据成员指定初值。也就是说,成员初始化列表是在数据成员定义的同时赋初值,但是构造函的函数体是采用先定义后赋值的方式来做。这样的区别就造成了,在有些场景下,是必须要使用成员初始化列表的。
1、初始化一个引用成员变量
2、初始化一个const变量
3、当我们在初始化一个子类对象的时候,而这个子类对象的父类有一个显示的带有参数的构造函数
4、当调用一个类类型成员的构造函数,而它拥有一组参数的时候
在这四种情况下是必须要使用成员初始化列表来为这些类型的成员赋初值的。因为这些成员都必须用初始化的方式赋初值。比如引用和const成员变量,这些都是不接受定义之后的赋值的。而对于3的话,是因为子类对象包括父类对象,父类对象既然明确指定了带参的构造函数,那么就必须在构造子类对象的父类部分时显示调用这个构造函数,是不能依赖于合成的默认构造函数的,而这样的话,就必须在成员初始化列表中调用。4也一样,类类型的成员所在类如果有显示定义的构造函数那么也是需要在定义这个成员的同时需要显示调用的。
对于第四点,需要注意的是当类中有其他类类型的成员变量时,只有当成员是该类类型时才需要初值化列表,若是该类类型的指针或是以该类作为vector,queue等容器的类型时,也是不需要的,因为这里该成员变量类行是容器类型,不是纯粹的该类类型。
成员初值化构造函数要不要参数由成员决定,当需要从外面引入参数时就加。
大多数情况下成员初值化是更加节省资源的,因此推荐使用。
C++成员初始化列表详解
十三、C++支持的4种变量存储类型
在C++中支持4种变量存储类型:
1、auto 自动变量 2、register 寄存器变量 3、extern 外部变量 4、static 静态变量
auto:auto称为自动变量(局部变量)。局部变量是指在函数内部说明的变量。所有的非全局变量都被认为是局部变量,所以auto实际上从来不用。局部变量在函数调用时自动产生,但不会自动初始化,随函数调用的结束,这个变量也就自动消失了。下次调用此函数时再自动产生,还要再赋值,退出时又自动消失。
static:static称为静态变量。根据变量的类型可以分为静态局部变量和静态全局变量。
1. 静态局部变量:它与局部变量的区别在于:在函数退出时,这个变量始终存在,但不能被其它函数使用;当再次进入该函数时,将保存上次的结果。其它与局部变量一样。
2. 静态全局变量:只在定义它的源文件中可见而在其它源文件中不可见的变量。它与全局变量的区别是:全局变量可以被其它源文件使用,而静态全局变量只能被所在的源文件使用。
extern:extern称为外部申明。全局变量是在函数的外部定义的,它的作用域为从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为本文件中各个函数所引用。为了使变量或者函数除了在定义它的源文件中可以使用外,还可以被其它文件使用,需要用extern来声明全局变量,以扩展全局变量的作用域。
register:register称为寄存器变量。它只能用于整型和字符型变量。定义符register说明的变量被存储在CPU的寄存器中,定义一个整型寄存器变量可写成: register int a;
对于以上四种数据的存储位置:register变量存在CPU的寄存器中;auto类型变量存在内存的栈;static型的局部变量和全局变量以及extern型变量(即全局变量),存在于内存的静态区。
十四、动态链接和静态链接的区别
库(lib):所谓的库就是一些功能代码经过编译连接后的可执行形式。
在编写代码的时候,经常会遇到需要使用别人已经写好的代码的情况,即利用别人的程序库在自己的代码中实现部分功能,这就涉及到静态链接和动态链接了。
静态链接:就是在生成可执行文件的时候,把所需要的函数的二进制代码包含到可执行文件中去。在程序发布的时候就不需要的依赖库,也就是不再需要带着库一块发布,程序可以独立执行。程序体积会相对大一些。如果静态库有更新的话,所有可执行文件都得重新链接才能用上新的静态库。
动态链接:就是在编译的时候不直接拷贝可执行代码,而是通过记录一系列的符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,当程序运行到指定代码时,去共享执行已经加载到内存中的动态可执行代码,最终达到运行时链接的目的。多个程序可以共享同一段代码,而不需要在磁盘上存储多个拷贝。由于是运行时加载,可能会影响程序的前期执行性能。
windows的dll文件和linux的os等都是动态库;lib,A库等为静态库,其中也包含有动态库。
静态链接与动态链接的区别
十五、C++ 值传递、指针传递、引用传递
值传递:形参是实参的拷贝,改变形参的值并不会影响外部实参的值,即形参和实参是相互独立的,只不过形参用了实参进行赋值。
指针传递:也是一种值传递,不过传递的是指向数据的地址,函数生成一个拷贝指针,和实参指针指向相同的数据,当更改指针指向的值时,会实时传递到实际的参数中去,可以用来修改并返回值,同时指针本身的值也可以改变,指向其他的值,这时不会影响到实参。
引用传递:传递的只是实参的一个别名,在函数中的任何修改,都会反映到视差中去,可以视为实际处理的就是实参。
指针与引用的:都是地址的概念;指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。区别:指针是一个实体,而引用仅是个别名;引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;引用没有const,指针有const,const的指针不可变;引用不能为空,指针可以为空;“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;指针和引用的自增(++)运算意义不一样;引用是类型安全的,而指针不是 (引用比指针多了类型检查)。
十六、C++多态实现机制
C++中的多态性具体体现在运行和编译两个方面:
运行时多态:是动态多态,其具体引用的对象在运行时才能确定。般通过虚函数(virtual function)+继承实现。
编译时多态:是静态多态,在编译时就可以确定对象使用的形式,例如函数重载、运算符重载,模板等。
一般讨论C++多态实现都是指运行时多态。它是通过虚函数和继承来实现的,实际上是通过虚表来实现的;在C++语言中,每个有虚函数的类或者虚继承的子类,编译器都会为它生成一个虚拟函数表(简称:虚表),虚表是从属于类的,表中的每一个元素都指向一个虚函数的地址。此外,编译器会为包含虚函数的类加上一个成员变量,是一个指向该虚函数表的指针(常被称为vptr),每一个由此类别派生出来的类,都有这么一个vptr。虚表指针是从属于对象的,也就是说,如果一个类含有虚表,则该类的所有对象都会含有一个虚表指针,并且该虚表指针指向同一个虚表。基类有虚函数的时候,类中有虚函数指针,指向虚表,该虚表中是基类的函数实现版本。如果子类有自己的实现,会在自己的虚函数表中替换子类的实现版本。当通过指针访问时候,通过vptr定位到的函数就是具体的类的实现版本,就是多态。
c++多态实现的机制
十七、new和malloc的异同
两者都是动态申请内存空间,new是C++里的操作符,用于动态创建数组或单个对象,返回相应的指针,利用delete释放内存;而malloc是C里的函数,申请一个内存空间返回void *指针指向内存空间,利用free来释放。主要区别:
1、new 返回指定类型的指针,并且可以自动计算所需要大小。而 malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。
2、malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。new创建的对象可以用初始化变量的方式初始化。
十八、static
静态全局变量:
静态局部变量:
静态函数:
在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
类中的static:
静态数据成员:
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员
同全局变量相比,使用静态数据成员有两个优势:
静态成员函数:
与静态数据成员一样,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。
十九、程序的内存分布
一个程序运行过程中占用的内存中主要有5个部分:
int main(){char* ch = "what's the f";...}
//ch为指针是保存在栈中的,但是字符串what's...则是在文件常量区的,因此不能更改ch指向的值,但是可以更改ch的值,使之指向其他地方
二十、C和C++区别
C:C语言是一种结构化语言,重点在于算法和数据结构,首要考虑的是如何通过一个过程对输入进行运算处理得到输出,是面向过程的。
C++:C++兼容C,首要考虑的是如何构造一个对象模型,通过对象来进行相应的处理得到输出,是面向对象的。
C++的特性:与C语言相比,C++有几大特性:继承、封装、多态。
封装:封装是把过程和数据包围起来,对数据的访问只能通过定义的方式进行,封装可以隐藏实现细节,使得代码模块化,并提供了代码重用性,可以隔离数据,保护内部数据不被随意修改。
继承:子类通过继承父类可以得到父类的部分特性,可以扩展已存在的代码模块,提高代码的重用性。
多态:就是为了接口重用,即同一个接口根据对象类型的不同可以调用不同的方法。
STL是C++提供的一个标准库,封装了许多容器和算法,具有很强大的功能,方便实用。
二十一、深复制和浅复制
在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存,采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误。
二十二、extern
extern 变量;声明外部全局变量,说明变量在外边文件定义
extern C;告诉编译器下面的代码按C语言进行编译,而不是C++,C++与C的编译区别主要在于C++函数支持重载,函数编译后的代码中不仅有函数的名称,还有函数的参数类型,而C一般只有函数的名称。
http://www.jianshu.com/p/5d2eeeb93590