C++学习笔记一

文章来源: (https://www.jianshu.com/p/0a99374646bc)

struct与class的区别

  • C的struct与C++的class的区别:struct只是作为一种复杂数据类型定义,不能用于面向对象编程。
  • C++中的struct和class的区别:对于成员访问权限以及继承方式,class中默认的是private的,而struct中则是public的。class还可以用于表示模板类型,struct则不行。

定义一个空的类型,无任何成员变量和成员函数。在该类型中添加一个构造函数和析构函数。把析构函数标记为虚函数呢?一个只含有虚函数的类的size为多少

  • sizeof(空类) = 1;
    因为一个空类也要实例化,所谓类的实例化就是在内存中分配一块地址,每个实例在内存中都有独一无二的地址。同样空类也会被实例化,所以编译器会给空类隐含的添加一个字节,这样空类实例化之后就有了独一无二的地址了。所以空类的sizeof为1。
  • 析构函数,跟构造函数这些成员函数,是跟sizeof无关的,也不难理解因为我们的sizeof是针对实例,而普通成员函数,是针对类体的,一个类的成员函数,多个实例也共用相同的函数指针,所以自然不能归为实例的大小
  • 虚函数为4,因为有虚函数表

含虚函数类的虚函数表是存放在哪里?

  • 虚函数表是全局共享的元素,即全局仅有一个.

  • 虚函数表类似一个数组,类对象中存储vptr指针,指向虚函数表.即虚函数表不是函数,不是程序代码,不肯能存储在代码段.

  • 虚函数表存储虚函数的地址,即虚函数表的元素是指向类成员函数的指针,而类中虚函数的个数在编译时期可以确定,即虚函数表的大小可以确定,即大小是在编译时期确定的,不必动态分配内存空间存储虚函数表,所以不再堆中.根据以上特征,虚函数表类似于类中静态成员变量.静态成员变量也是全局共享,大小确定.所以虚函数表和静态成员变量一样,存放在全局数据区.

  • c/c++程序所占用的内存一共分为五种:
    栈区,堆区,程序代码区,全局数据区(静态区),文字常量区.
    显而易见,虚函数表存放在全局数据区.

  • 虚函数表是class specific的,也就是针对一个类来说的,这里有点像一个类里面的staic成员变量,即它是属于一个类所有对象的,不是属于某一个对象特有的,是一个类所有对象共有的。

  • 虚函数表是编译器来选择实现的,编译器的种类不同,可能实现方式不一样,就像前面我们说的vptr在一个对象的最前面,但是也有其他实现方式,不过目前gcc 和微软的编译器都是将vptr放在对象内存布局的最前面。
    虽然我们知道vptr指向虚函数表,那么虚函数表具体存放在内存哪个位置呢,虽然这里我们已经可以得到虚函数表的地址。实际上虚函数指针是在构造函数执行时初始化的,而虚函数表是存放在可执行文件中的。下面的一篇博客测试了微软的编译器将虚函数表存放在了目标文件或者可执行文件的常量段中,http://blog.csdn.net/vicness/article/details/3962767,不过我在gcc下的汇编文件中没有找到vtbl的具体存放位置,主要是对可执行文件的装载和运行原理还没有深刻的理解,相信不久有了这些知识之后会很轻松的找到虚函数表到底存放在目标文件的哪一个段中。
    经过测试,在gcc编译器的实现中虚函数表vtable存放在可执行文件的只读数据段.rodata中。

C++虚函数的具体实现原理

基类定义了虚函数,子类可以重写该函数,当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态地调用属于子类的该函数,且这样的函数调用是无法在编译期间确认的,而是在运行期确认,也叫做迟绑定。在此模型中,non static 数据成员被放置到对象内部,static数据成员, static and nonstatic 函数成员均被放到对象之外。对于虚函数的支持则分两步完成:
1.每一个class产生一堆指向虚函数的指针,放在表格之中。这个表格称之为虚函数表(virtual table,vtbl)。
2.每一个对象被添加了一个指针,指向相关的虚函数表vtbl。通常这个指针被称为vptr。vptr的设定和重置都由每一个class的构造函数,析构函数和拷贝赋值运算符自动完成。

介绍C++内存管理(C ++的内存模型是热门问题)

在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

  • 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。分配方式类似链表。
  • 自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
    栈,由编译器自动分配和释放,存放函数的参数值、局部变量的值等。* 栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  • 全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
  • 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

C/C++的区别?

  • 首先 C 包含 C 几乎全部功能。
  • C 比 C 多面向对象语言部分。
  • C 比 C 多泛型编程部分。
  • C 比 C 多 STL 部分。

面向对象的特点,简单描述一下?

一、三个基本特征
向对象的三个基本特征是:封装、继承、多态。

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。
继承概念的实现方式有三类:实现继承、接口继承和可视继承。
Ø 实现继承是指使用基类的属性和方法而无需额外编码的能力;
Ø 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
Ø 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。

多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

实现多态,有二种方式,覆盖,重载。
覆盖,是指子类重新定义父类的虚函数的做法。
重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)

inline函数的作用

C++内联函数提供了替代函数调用的方案,通过inline声明,编译器首先在函数调用处使用函数体本身语句替换了函数调用语句,然后编译替换后的代码。因此,通过内联函数,编译器不需要跳转到内存其他地址去执行函数调用,也不需要保留函数调用时的现场数据。

虚函数的作用

虚函数的作用就是实现“动态联编”,也就是在程序的运行阶段动态地选择合适的成员函数。具体的实现方式是:在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。编译器在编译过程中发现类的函数名前的关键字virtual后,会自动将其作为动态联编处理,即在程序运行时动态地选择合适的成员函数,

纯虚函数的作用,纯虚函数能否被实例化

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”virtual void funtion1()=0

引入原因:
1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

含有纯虚函数的类为抽象类,不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类的所有虚函数,否则派生类也是抽象类,不能实例化对象。
抽象类不能定义对象,但是可以作为指针或或者引用类型使用。

类中哪些函数不能声明为虚函数,原因

常见的不不能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。

  • 普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。
  • 构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。
  • 内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的邦定函数)
  • 静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他也没有要动态邦定的必要性。
  • 因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。

析构函数能否为虚函数,为什么?

有多态的基类应该声明一个virtual析构函数。如果class带有任何virtual函数,他就应该拥有一个virtual析构函数。若基类的析构函数不是虚函数,当基类指针pa指向用new运算符生成的派生类对象B时,delete基类指针,只会运行基类的析构函数,而不会执行派生类的析构函数。

你对c++内存这里有什么见解,或者有什么好的设计理念

自己总结了几点,new,detele,new[],delete[],构造析购里面通常怎么设计,虚析构函数的概念 ,后来他又给我讲解了一下c++喜欢用namespace这种机制,类似于析购构造的原理,一段结束后自动释放的原理。
我接着也说了一下自己对namespace经常用到的地方(1)自动锁的原理经常用到namespace,起到自动调用析购的函数(2)还有喜欢把一个class也封装在一个namespace里面,

实现编译器处理虚函数表应该如何处理

C++内存分配

堆、栈、自由存储区、全局/静态存储区和常量存储区

程序编译链接的过程和函数找不到在哪个阶段报错

动态绑定的介绍

动态绑定:运行时绑定,通过地址实现
只有采用“指针->函数()”或“引用变量.函数()”的方式调用C++类中的虚函数才会执行动态绑定。对于C++中的非虚函数,因为其不具备动态绑定的特征,所以不管采用什么样的方式调用,都不会执行动态绑定。
即所谓动态绑定,就是基类的指针或引用有可能指向不同的派生类对象,对于非虚函数,执行时实际调用该函数的对象类型即为该指针或引用的静态类型(基类类型);而对于虚函数,执行时实际调用该函数的对象类型为该指针或引用所指对象的实际类型。

引用是否能实现动态绑定,为什么引用可以实现

可以,引用和指针可以实现动态绑定

介绍所有的构造函数

默认构造函数
构造函数
复制构造函数

什么情况下要给类写拷贝构造函数

深拷贝时。类具有指针成员时。

成员初始化列表的概念

构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
2.const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。

为什么用成员初始化列表会快一些

初始化列表是直接初始化,利用的函数匹配原理,调用的是构造函数或者copy构造函数(根据初始化的参数来决定,内置类型为构造函数,类类型为copy构造函数)。在函数体里复制,会多调用一次默认构造函数。

C语言变量存放位置

C++垃圾回收,shared_ptr的引用计数出现循环引用怎么办

C中的malloc和free做了哪些事情,free怎么知道free多长,C++中的delete又怎么知道delete多长

在heap中进行内存分配,以链表形式进行记录。free时先判断是不是malloc分配的,然后释放。将free标志设置为1

指针和引用的区别?

  1. 引用必须被初始化,指针不必。
  2. 引用初始化以后不能被改变,指针可以改变所指的对象。
  3. 不存在指向空值的引用,但是存在指向空值的指针。

6野指针的问题。

1、指针变量没有被初始化。 任何指针变量在刚被创建的时候不会自动成为NULL指针,它的缺省值是随机的。所以指针变量在创建的时候,要么设置为NULL,要么指向合法的内存。
2、指针p被free/delete之后,没有置为NULL(最好加一句p = NULL;)。他们只是把指针指向的内存给释放掉,并没有把指针本身干掉。
3、指针操作超越了变量的作用范围。不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

指针(各种指针,指针数组,数组指针,函数指针,n级指针)

char * a=new char a[]; sizeof(a)大小

4字节,指针的大小。

静态动态数组区别

动态数组可以再编译时确定数组大小。存储的位置不一样。

new delete new[] delete [] malloc free底层实现

http://blog.codinglabs.org/articles/a-malloc-tutorial.html

overlode override
77.unsigned long* p1=(unsigned long* )0x810000(地址)
unsigned char* p2=(unsigned char* )0x810000(地址)
p1+5=? p2+5=?

能否用memset实例化一个类?

不能,禁止用memcpy、memset初始化非POD对象。由于非POD类型比如非集合类型的class对象,可能存在虚函数,内存布局不确定,跟编译器有关,滥用内存拷贝可能会导致严重的问题。
无法复制虚函数表。

new/delete和malloc/free的区别

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

* malloc/free的使用要点

void * malloc(size_t size);
int *p = (int *) malloc(sizeof(int) * length);

我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。

  • malloc返回值的类型是void * ,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。
  • malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。我们通常记不住int, float等数据类型的变量的确切字节数。例如int变量在16位系统下是2个字节,在32位下是4个字节;而float变量在16位系统下是4个字节,在32位下也是4个字节。

* new/delete的使用要点

int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];

这是因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。

链表和数组的区别

const 和 static 用法。

这个比较多, 详细的可以自己在网上找找。
• const 关键字: 用在局部变量上, 用在类成员变量上, 用在类成员函数后面…
• static 关键字: 用在全局变量上, 用在局部变量上,用在类成员变量上, 用在类成员函数上…

计算下面几个类的大小

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; 

已知某类:

class A
{
public:
A(int x){}
}
问:A a = 1;是否正确, 如果正确, 那么它调用了哪些函数?
• 由于 A 类的构造函数没有声明为explicit因此可以 int 类型强制转换来。因此, 该表达式正确。
• 对于没有优化过的编译器: 先将 1 转化为 A 类型(构造函数), 再将其赋值给 a 变量(拷贝赋值函数)。
• 对于优化了的编译器: 直接用 1 来构造变量 a(构造函数)。

代码找错:

char* str = “hello”; 1)
char* p = new char[5]; 2)
strcpy(p, str); 3)
cout << p << endl; 4)
• 编译通过, 运行时出错。
• 2) 中的数组申请太小, 无法容纳下 “hello” 的大小。 因为字符串后面还有结束符(‘\0’)。
• 与 new 搭配的是 delete。2) 中 new 了资源没有 delete 掉。

class {}内部实现的函数。

默认构造函数, 默认析构函数, 拷贝赋值函数, (移动构造函数, 移动赋值函数) 析构函数,赋值运算符

C 语言的特性

C 是面向对象的语言, 自然有面向对象的语言的特性: 封装, 继承, 多态。
C 也是泛型编程语言, 自然也有泛型编程语言的特性。

重载和重写的区别

重载要求相同的函数名, 不同的参数列表。仅返回值不同的函数不能重载。
重写要求父类是虚函数, 子类改写其函数体。要求函数签名一模一样。

对象复用的了解
零拷贝的了解

手写实现智能指针类
1,方法重载,何时用重载,为什么要使用重载?而不是把一个方法名字换成不同的。
菱形继承与接口目的
• 面向对象的三大特性
• MVC设计模式

猜你喜欢

转载自blog.csdn.net/qq_34374664/article/details/82017736