c++基础知识回顾

本文详细阐述了C++中的继承机制,强调了虚析构函数的重要性,防止内存泄漏。讨论了父类指针与子类指针的各种情况,解释了静态联编与动态联编的概念。还深入探讨了虚函数的本质、作用以及纯虚函数和抽象类的应用。此外,文章提到了C++类的内存占用、内存区域划分以及数据类型的转换,并介绍了智能指针在避免悬垂指针问题上的作用。

1、在类的继承机制中,父类的析构函数要设置成虚析构函数

父类指针绑定子类对象,若父类中析构函数不是虚函数,则静态编译,只会调用父类的析构函数不会调用子类的析构函数,所以会造成内存泄露!

若父类析构函数为虚函数,则指针会动态编译,调用子类的析构函数,调用子类的析构函数后会自动调用父类的析构函数。(子类对象创建会自动调用父类的构造函数,先调用父类的构造函数!子类析构函数调用完成后会自动调用父类的析构函数)。


2、父类指针和子类指针几种情况分析

(1)父类指针绑定父类对象,完全正常使用。

(2)子类指针绑定子类对象,完全正常使用。

(3)父类指针引用子类对象,只能引用父类中的函数(除了虚函数)。父类指针肯定不能调用子类函数(静态属性就不能)。

(4)子类指针引用父类对象,子类中不涉及子类内部数据成员的函数可以通过该指针调用,父类函数均可调用,涉及内部数据的会调用成功但执行失败。

(5)一个空指针也可以调用函数!指针为空,指向一个类(用dynamic_ cast,父类项子类的转换没有虚函数,转换失败,返回空指针),可以直接调用没有数据成员的函数(因为代码段是共有的,也就是函数是共有的)。但是涉及数据成员的函数会调用成功但是执行失败。

(6)父类指针指向子类对象(没有虚析构函数),销毁父类指针会造成内存泄漏。原因如1.

(7)内存越界:子类指针指向父类对象,销毁子类指针会造成内存越界!

fu *pfu = new fu;
zi *pzi = static_cast<zi*>(pfu);
delete pzi; //有时出错,有时无错

所以c++不允许子类指针绑定到父类对象!

3、静态联编和动态联编

静态联编:是程序匹配连接在编译阶段实现,例如:重载函数。

动态联编:程序匹配实在运行时进行,例如:switch,if。

4、虚函数

(1)虚函数的本质:本质是一个函数指针(32位系统占用4字节)。

(2)存在虚函数的类都有一个一维的虚函数表,表中存放虚函数地址,类内必须保存这个虚表的起始地址,所以类存在一个指向虚表的指针,但不管类中有几个虚函数,类内部只保存虚表的起始地址,虚函数地址可由偏移算法得到。

(3)虚函数作用:基类指针指向不同的派生类对象,实现不同的功能。基类指针提供一个接口作用。

一个接口实现多个功能,可以实现需求扩展。

(4)构造函数不能为虚函数。因为违背继承。若构造函数为虚函数基类指针指向子类对象只会调用子类的构造函数不会调用父类构造函数。

(5)虚析构函数:适配器模式,让父类指针正确释放子类对象的内存。防止内存泄漏。

(6)虚函数必须要定义。

(7)虚函数实现运行时多态。父类指针绑定到子类对象。

5、纯虚函数和抽象类

(1)形式:virtual 类型名 函数名(参数表) =  0;

(2)一个具有纯虚函数的基类称为抽象类,不能创建对象,实例化,不能作为返回值类型,参数类型。抽象类存在就是为了提供接口。可以创建指针!

(3)纯虚函数在基类中没有定义,任何派生类中都提供自己的定义,否则变成抽象类(只要有一个纯虚函数没有定义)。

6、类所占内存

(1)类所占内存是由数据成员和虚函数共同决定。

(2)成员函数是所有类对象所共有的,不在类的对象中。访问函数是由this指针指向table,table中记录了各个成员函数的地址。

(3)空类占一个字节。空类同样可以实例化!

(4)内存对齐:每个数据成员所占内存起始地址是该数据成员类型所占内存的整数倍;

   类所占内存是类内最大成员所占内存的整数倍,不够在最后一个成员后补齐;

struct T{

char a;

  int *d;

   int b;

int c:16;

double e;

}

在64位系统中T站多少字节?

a*******d(8个字节)b(4个字节)c(2个字节)**e(8个字节)。一共32个字节。c:16表示位域表示一共占16位!

7、程序的内存区域

在c语言中:

栈区:由编译器自动分配和释放,存放函数的参数值,局部变量等。函数参数的压栈顺序从右往左,就是函数参数的访问顺序是从右往左的;

堆区:程序员申请和释放。若程序员不释放在程序结束后可能由系统回收。堆频繁的创建释放会造成内存空间不连续;

全局(静态)区:全局变量和静态变量。初始化全局和静态在一块,未初始化全局和静态在另一块由系统释放。在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。全局变量可以跨文件使用,而静态全局变量只能限定在本文件可以访问

常量区:字符串常量和其他常量。

在c++语言中:内存是5区域的。栈,堆,自由存储,全局/静态,常量。

堆:是由new创建,delete释放。内置数据类型均可由delete释放,不论是否为数组。为数组最好用delete []释放。

自由存储:malloc创建free释放。

(2)栈区是向下生长的。栈底为高地址,在它以后生成变量的地址都比前一个变量地址低!所以栈顶指针向着内存减少方向生长。而数组a[10]中a[0]肯定是低地址a[10]是高地址。

堆区内存地址是向上生长的。

(3)c语言内存变量的存储类别:

auto(自动分配释放),static(静态),extern(全局),register(寄存器,不可取址)。

(4)c语言中变量可以声明很多次,但只能定义一次!

局部变量:Int a; int a  = 10; 两种方式都看成是定义,在内存中开辟实体。

全局变量:既有声明也有定义。int a; int a; int a; 可以编译通过,声明和定义的区别!

8、重载、覆盖、隐藏

(1)重载:在同一个作用域中声明的几个具有不同参数的(参数类型,个数,顺序不同)的同名函数。除了返回类型不同其他都相同的,不叫重载会出错!

(2)覆盖:指在派生类中重新定义了父类存在的函数,其函数名,形参列表,返回类型都与父类完全相同,只有函数体不同。此函数必须为虚函数。

(3)隐藏:派生类函数屏蔽了与其同名的父类函数。是否有virtual关键字。

第一种情况:若派生类和基类函数同名,但参数不同,此时不论有无virtual关键字,基类的函数都被隐藏。

第二种情况:函数名相同,参数也相同,但是基类此函数没有virtual关键字,此函数被隐藏。

9、数据类型转换

基本类型<---->基本类型,基本类型<---->类类型,类类型<---->类类型

10、几个指针

(1)数组名相当于常量指针。

(2)sizeof一个数组名返回整个数组所占内存大小。sizeof是一个操作符不是函数。

(3)悬垂指针:指向曾经存在的对象,该对象现在不存在了。会导致未定义的行为。

解决方法:引用智能指针。

(4)哑指针:就是传统的c\c++指针。

(5)野指针:指向一块垃圾内存(不可用内存)的指针。不是NULL指针。NULL指针可用于条件判读if。但是野指针不可以。

产生原因:

任何指针没有初始化,任何指针变量创建时都不会被自动初始化为NULL,而是随机值 。

指针p被delete或free后没有被赋值NULL。

(6)智能指针

当类有指针成员时,一般采用两种方式管理指针:第一种值型管理方式,每个指针都指向每个指针都保留一份指针指向对象的拷贝。第二种智能指针,从而实现不同对象的数据共享。

double *p = new double; auto_ptr<double> autop(p);

用autop来管理p指向内存,不用delete p 会自动管理。

sharedptr。

11、c++转换

c++转换四个关键字

const_cast、static_cast、reinterpret_case、dynamic_cast

const_cast:const_cast<T>(f),T要转换成的类型,f待转化的类型。用于去掉cost属性。把const类型转换成非const类型。

static_cast:(1)类指针之间转换,只是单纯的父类指针和子类指针的相互转换。而类类型对象之间的转换只能为转换构造函数和类型转换函数两种形式。父类和子类指针之间的转换或都可以成功。

(2)内置类型之间的转换:double db = 3.14; int i = static_cast<int>(db);

dynamic_cast:基类中一定要有虚函数,只能在多态指针中完成正确的转换。

(1)从子类指针向父类指针之间的转换dynamic-cast和static_cast都可以,都能转换成功。若析构函数不是虚函数delete该指针会造成内存泄漏。

(2)从父类指针向子类指针转换,static_cast为非空,而dynamic_cast为空NULL。delete该指针会造成内存越界。

(3)从父类引用到子类引用转换,static_cast正常转换,dynamic_cast抛出异常。

reinterpert_cast:(1)内置数据类型转换为其他数据类型,但内置数据类型不能转换为类类型。

(2)任何类型指针(包括类类型)之间都可转换。

类对象转换:

两种形式:构造函数,转换函数。

类型转换函数:

类类型转换成基本数据类型;类和类之间转换。

X::operator T() {}
T为要转换成的类型。函数没有返回值和形参。只能为成员函数,不能为友元函数。必须有return语句,返回T类型对象。

构造函数:类型转换构造函数。

隐式非explict函数,可用于隐式转换。

fushu f1 = 10;
从派生类到基类的转换
(1)非对象转换:基类指针或引用绑定到派生类对象上。

(2)对象转换:用派生类对象对基类对象赋值和初始化。

从基类到派生类转换

指针转换
(1)数组转换成指向首地址的指针,该指针是常量指针。

(2)const任意对象指针只能转换成const void *,不能转换成void *,其余指针均可转换成void *。

(3)NULL相当于int 0,而nullptr可转换成任意类型指针。

void fun(int a) {cout << "整形" << endl;}
void fun(int *p) {cout << "指针" << endl;}
fun(NULL); //输出 整形
fun(nullptr)//输出 指针
(4)指针可转换成bool型

iostream对象可转换成bool型

12、c++是强类型语言,但不是类型安全语言,java是类型安全语言。c++强类型严格检查类型匹配。

13、动态内存

(1)使用动态内存的3个有原因:

程序不知道自己要使用几个对象;

     程序不知道所需对象的具体类型;

     许多对象共享底层数据。

(2) 

int *pi1 = new int; //默认初始化,*pi1值没有定义
int *pi2 = new int();//值初始化,*pi2值为0
int *pi3 = new int(0);//直接初始化,*pi3值为0

(3)动态数组


14、深拷贝和浅拷贝

简单的说,有指针的情况下浅拷贝就是把新的指针指向已存在的内存,该内存已经有指针指向。深拷贝就是申请一块新的内存然后将新指针指向这块新内存。

编译器默认生成的拷贝构造函数是浅拷贝的!

15、MyClass是一个类,MyClass a[4], *p[5];调用几次构造函数

只在定义a[4]是调用构造函数,因为声明指针是不需要类的定义只需要类的声明即可。声明指针只是表示你可以指向的内存空间,而没有定义内存空间。

16、赋值(=),下标([])、调用(())、成员访问箭头(->)运算符必须是成员函数

17、在c++中,为了让某个类只能通过new来创建(即如果直接创建对象,编译器将报错)

将析构函数设为私有。因为在new的过程中,编译器不会检查析构函数是否可访问,但是要注意的是,这导致了不能调用delete删除该对象占用内存空间,所以需要在该类的内部自己增加一个成员函数,在该自定义函数内部使用delete this的方式清除对象的内存。

18、多态

c++语言多态性分为编译时的多态性和运行时的多态性。函数模板和函数重载都是编译时的多态性。基类指针指向子类对象,该指针调用虚函数是运行时多态性,又称动态绑定。

19、友元

class B;
class A {

public:
    virtual void f1( B &) ;
}

class B {

public:
    int c = 3;
    friend void A::f1( B & );
}

void A::f1( B &b ) {

    cout << b.c << endl;
}

void main() {

    class B b;
    class A a;
    a.f1(b);
    //b.f1(b); 这个是错误的!友元的定义!

}

//输出 3;
<span style="white-space:pre">	</span>1、友元的定义是,f1是A的函数,是B类的友元函数,所以f1可以调用B类的私有数据成员,并不是说f1是B类的函数,所以b.f1是错误的!B类中没有这个成员。
<span style="white-space:pre">	</span>2、A类的虚函数可以声明称B类的友元函数,但在B类中f1函数不能声明称B类的虚函数!<strong>友元函数不是类的成员函数,不能被继承,所以不能声明为虚函数!</strong>
<strong><span style="white-space:pre">	</span>3、前向声明 </strong>
<span style="font-weight: bold; white-space: pre;">	</span>在一开始class B; 是一个前向声明,它向程序引入了名字B,并指明B是一个类类型。对于B类来说,它声明之后定义之前是一个不完全类型。
<span style="white-space:pre">	</span>不完全类型可以定义指向这个类型的指针和引用,也可以<strong>声明但不能定义</strong>以这种不完全类型作为参数或者返回类型的函数。(所以在这个友元函数f1中,我们只能在A类体中声明f1,只有在B类的定义完成后才能定义f1)
20、c++中不能被声明为虚函数有哪些?

参考:http://blog.csdn.net/ta893115871/article/details/8194836

普通函数(非成员函数)、静态(成员)函数、内联成员函数、友元函数。

1、普通函数(非成员函数)不能声明为友元函数

普通成员数只能被重载(overload)而不能被继承(override)。编译器在编译时就会绑定函数,不是成员函数的不能被继承!

2、构造函数不能是虚函数

21、继承


























   

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值