网站seo诊断分析报告,美橙互联网站建设案例,零基础网站建设视频教程,国外网站搭建平台点击蓝字关注我们第一部分#xff1a;计算机基础1. C/C内存有哪几种类型#xff1f;C中#xff0c;内存分为5个区#xff1a;堆(malloc)、栈(如局部变量、函数参数)、程序代码区#xff08;存放二进制代码#xff09;、全局/静态存储区#xff08;全局变量、static变量计算机基础1. C/C内存有哪几种类型C中内存分为5个区堆(malloc)、栈(如局部变量、函数参数)、程序代码区存放二进制代码、全局/静态存储区全局变量、static变量和常量存储区常量。此外C中有自由存储区new一说。 全局变量、static变量会初始化为缺省值而堆和栈上的变量是随机的不确定的。2. 堆和栈的区别1).堆存放动态分配的对象——即那些在程序运行时动态分配的对象比如 new 出来的对象其生存期由程序控制2).栈用来保存定义在函数内的非static对象如局部变量仅在其定义的程序块运行时才存在3).静态内存用来保存static对象类static数据成员以及定义在任何函数外部的变量static对象在使用之前分配程序结束时销毁4).栈和静态内存的对象由编译器自动创建和销毁。3. 堆和自由存储区的区别总的来说堆是C语言和操作系统的术语是操作系统维护的一块动态分配内存自由存储是C中通过new与delete动态分配和释放对象的抽象概念。他们并不是完全一样。 从技术上来说堆heap是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存它提供了动态分配的功能当运行程序调用malloc()时就会从中分配稍后调用free可把内存交还。而自由存储是C中通过new和delete动态分配和释放对象的抽象概念通过new来申请的内存区域可称为自由存储区。基本上所有的C编译器默认使用堆来实现自由存储也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现这时藉由new运算符分配的对象说它在堆上也对说它在自由存储区上也正确。4. 程序编译的过程程序编译的过程中就是将用户的文本形式的源代码(c/c)转化成计算机可以直接执行的机器代码的过程。主要经过四个过程预处理、编译、汇编和链接。具体示例如下。 一个hello.c的c语言程序如下。#include stdio.h
int main()
{printf(happy new year!\n);return 0;
}其编译过程如下 在这里插入图片描述5. 计算机内部如何存储负数和浮点数负数比较容易就是通过一个标志位和补码来表示。 拓展问题什么是补码 负数补码为反码加1 正数补码为原码负数为什么用补码 统一加减法正负零问题对于浮点类型的数据采用单精度类型float和双精度类型(double)来存储float数据占用32bit,double数据占用64bit,我们在声明一个变量float f 2.25f的时候是如何分配内存的呢如果胡乱分配那世界岂不是乱套了么其实不论是float还是double在存储方式上都是遵从IEEE的规范的float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。更多可以参考浮点数表示。 无论是单精度还是双精度在存储中都分为三个部分6. 函数调用的过程如下结构的代码int main(void)
{...d fun(a, b, c);coutdendl;...return 0;
}调用fun()的过程大致如下main()1).参数拷贝压栈注意顺序是从右到左即c-b-a2).保存d fun(a, b, c)的下一条指令即coutdendl实际上是这条语句对应的汇编指令的起始位置;3).跳转到fun()函数注意到目前为止这些都是在main()中进行的fun()4).移动ebp、esp形成新的栈帧结构;5).压栈push形成临时变量并执行相关操作;6).return一个值;7).出栈pop;8).恢复main函数的栈帧结构;9).返回main函数;main()。。。7. 左值和右值不是很严谨的来说左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式)右值指的则是只能出现在等号右边的变量(或表达式)。举例来说我们定义的变量 a 就是一个左值而malloc返回的就是一个右值。或者左值就是在程序中能够寻址的东西右值就是一个具体的真实的值或者对象没法取到它的地址的东西(不完全准确)因此没法对右值进行赋值但是右值并非是不可修改的比如自己定义的class, 可以通过它的成员函数来修改右值。归纳一下就是可以取地址的有名字的非临时的就是左值不能取地址的没有名字的临时的通常生命周期就在某个表达式之内的就是右值8. 什么是内存泄漏面对内存泄漏和指针越界你有哪些方法你通常采用哪些方法来避免和减少这类错误用动态存储分配函数动态开辟的空间在使用完毕后未释放结果导致一直占据该内存单元即为内存泄露。1). 使用的时候要记得指针的长度.2). malloc的时候得确定在那里free.3). 对指针赋值的时候应该注意被赋值指针需要不需要释放.4). 动态分配内存的指针最好不要再次赋值.5). 在C中应该优先考虑使用智能指针.第二部分C v.s. C1. C和C的区别1). C是C的超集;2). C是一个结构化语言它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程对输入或环境条件进行运算处理得到输出或实现过程事务控制而对于C首要考虑的是如何构造一个对象模型让这个模型能够契合与之对应的问题域这样就可以通过获取对象的状态信息得到输出或实现过程事务控制。2. int fun() 和 int fun(void)的区别?这里考察的是c 中的默认类型机制。在c中int fun() 会解读为返回值为int(即使前面没有int也是如此但是在c中如果没有返回类型将报错)输入类型和个数没有限制 而int fun(void)则限制输入类型为一个void。在c下这两种情况都会解读为返回int类型输入void类型。3. const 有什么用途主要有三点1).定义只读变量或者常量只读变量和常量的区别参考下面一条;2).修饰函数的参数和函数的返回值;3).修饰函数的定义体这里的函数为类的成员函数被const修饰的成员函数代表不能修改成员变量的值因此const成员函数只能调用const成员函数 可以访问非const成员但是不能修改4).只读对象。只读对象只能调用const成员函数。class Screen {
public:
const char cha //const成员变量
char get() const; //const成员函数
};const Screen screen //只读对象4. 在C中用const 能定义真正意义上的常量吗C中的const呢不能。c中的const仅仅是从编译层来限定不允许对const 变量进行赋值操作在运行期是无效的所以并非是真正的常量比如通过指针对const变量是可以修改值的。但是c中是有区别的c在编译时会把const常量加入符号表以后仍然在编译期遇到这个变量会从符号表中查找所以在C中是不可能修改到const变量的。 补充1. c中的局部const常量存储在栈空间全局const常量存在只读存储区所以全局const常量也是无法修改的它是一个只读变量。2. 这里需要说明的是常量并非仅仅是不可修改而是相对于变量它的值在编译期已经决定而不是在运行时决定。3.c中的const 和宏定义是有区别的宏是在预编译期直接进行文本替换而const发生在编译期是可以进行类型检查和作用域检查的。4.c语言中只有enum可以实现真正的常量。5 ). c中只有用字面量初始化的const常量会被加入符号表而变量初始化的const常量依然只是只读变量。6. c中const成员为只读变量可以通过指针修改const成员的值另外const成员变量只能在初始化列表中进行初始化。下面我们通过代码来看看区别。 同样一段代码在c编译器下打印结果为*pa 4a 4 在c编译下打印的结果为 *pa 4 a 8int main(void)
{const int a 8;int *pa (int *)a;*pa 4;printf(*pa %d, a %d, *pa, a);return 0;
}另外值得一说的是由于c中const常量的值在编译期就已经决定下面的做法是OK的但是c中是编译通不过的。int main(void)
{const int a 8;const int b 2;int array[ab] {0};return 0;
}5. 宏和内联inline函数的比较1). 首先宏是C中引入的一种预处理功能2). 内联inline函数是C中引入的一个新的关键字C中推荐使用内联函数来替代宏代码片段3). 内联函数将函数体直接扩展到调用内联函数的地方这样减少了参数压栈跳转返回等过程4). 由于内联发生在编译阶段所以内联相较宏是有参数检查和返回值检查的因此使用起来更为安全5). 需要注意的是 inline会向编译期提出内联请求但是是否内联由编译器决定当然可以通过设置编译器强制使用内联6). 由于内联是一种优化方式在某些情况下即使没有显示的声明内联比如定义在class内部的方法编译器也可能将其作为内联函数。7). 内联函数不能过于复杂最初C限定不能有任何形式的循环不能有过多的条件判断不能对函数进行取地址操作等但是现在的编译器几乎没有什么限制基本都可以实现内联。 更多请参考inline关键字6. C中有了malloc / free , 为什么还需要 new / delete1). malloc与free是C/C语言的标准库函数new/delete是C的运算符。它们都可用于申请动态内存和释放内存。2). 对于非内部数据类型自定义类型的对象而言光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数对象在消亡之前要自动执行析构函数。 由于malloc/free是库函数而不是运算符不在编译器控制权限之内不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C语言需要一个能完成动态内存分配和初始化工作的运算符new以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。 最后补充一点题外话new 在申请内存的时候就可以初始化如下代码 而malloc是不允许的。另外由于malloc是库函数需要相应的库支持因此某些简易的平台可能不支持但是new就没有这个问题了因为new是C语言所自带的运算符。int *p new int(1);特别的在C中如下的代码用new创建一个对象(new 会触发构造函数 delete会触发析构函数)但是malloc仅仅申请了一个空间所以在C中引入new和delete来支持面向对象。#include cstdlib
class Test
{...
}Test* pn new Test;
Test* pm (Test*)malloc(sizeof(Test));7. C和C中的强制类型转换C中是直接在变量或者表达式前面加上小括号括起来的目标类型来进行转换一招走天下操作简单但是由于太过直接缺少检查因此容易发生编译检查不到错误而人工检查又及其难以发现的情况而C中引入了下面四种转换1). static_cast a. 用于基本类型间的转换 b. 不能用于基本类型指针间的转换 c. 用于有继承关系类对象间的转换和类指针间的转换2). dynamic_cast a. 用于有继承关系的类指针间的转换 b. 用于有交叉关系的类指针间的转换 c. 具有类型检查的功能 d. 需要虚函数的支持3). reinterpret_cast a. 用于指针间的类型转换 b. 用于整数和指针间的类型转换4). const_cast a. 用于去掉变量的const属性 b. 转换的目标类型必须是指针或者引用拓展 在C中普通类型可以通过类型转换构造函数转换为类类型那么类可以转换为普通类型吗答案是肯定的。但是在工程应用中一般不用类型转换函数因为无法抑制隐式的调用类型转换函数类型转换构造函数可以通过explicit来抑制其被隐式的调用而隐式调用经常是bug的来源。实际工程中替代的方式是定义一个普通函数通过显式的调用来达到类型转换的目的。class test{int m_value;...
public:operator int() //类型转换函数{return m_value;}int toInt() //显示调用普通函数来实现类型转换{return m_value}
}int main()
{...test a(5);int i a; // 相当于 int i test::operator int(a)...return 0;
}8. static 有什么用途1. 静态局部/全局变量2. 静态函数3. 类的静态数据成员4. 类的静态成员函数9. 类的静态成员变量和静态成员函数各有哪些特性静态成员变量1). 静态成员变量需要在类内声明加static在类外初始化不能加static如下例所示2). 静态成员变量在类外单独分配存储空间位于全局数据区因此静态成员变量的生命周期不依赖于类的某个对象而是所有类的对象共享静态成员变量3). 可以通过对象名直接访问公有静态成员变量4). 可以通过类名直接调用公有静态成员变量即不需要通过对象这一点是普通成员变量所不具备的。class example{
public:
static int m_int; //static成员变量
};int example::m_int 0; //没有staticcoutexample::m_int; //可以直接通过类名调用静态成员变量静态成员函数1). 静态成员函数是类所共享的2). 静态成员函数可以访问静态成员变量但是不能直接访问普通成员变量需要通过对象来访问需要注意的是普通成员函数既可以访问普通成员变量也可以访问静态成员变量3). 可以通过对象名直接访问公有静态成员函数4). 可以通过类名直接调用公有静态成员函数即不需要通过对象这一点是普通成员函数所不具备的。class example{
private:
static int m_int_s; //static成员变量
int m_int;
static int getI() //静态成员函数在普通成员函数前加static即可
{return m_int_s; //如果返回m_int则报错但是可以return d.m_int是合法的
}
};coutexample::getI(); //可以直接通过类名调用静态成员变量10. 在C程序中调用被C编译器编译后的函数为什么要加extern“C”?C语言支持函数重载C语言不支持函数重载函数被C编译器编译后在库中的名字与C语言的不同假设某个函数原型为void foo(int x, int y);该函数被C编译器编译后在库中的名字为 _foo, 而C编译器则会产生像: _foo_int_int 之类的名字。为了解决此类名字匹配的问题C提供了C链接交换指定符号 extern “C”。11. 头文件中的 ifndef/define/endif 是干什么用的? 该用法和 program once 的区别相同点: 它们的作用是防止头文件被重复包含。 不同点1). ifndef 由语言本身提供支持但是 program once 一般由编译器提供支持也就是说有可能出现编译器不支持的情况(主要是比较老的编译器)。2). 通常运行速度上 ifndef 一般慢于 program once特别是在大型项目上 区别会比较明显所以越来越多的编译器开始支持 program once。3). ifndef 作用于某一段被包含define 和 endif 之间的代码 而 program once 则是针对包含该语句的文件 这也是为什么 program once 速度更快的原因。4). 如果用 ifndef 包含某一段宏定义当这个宏名字出现“撞车”时可能会出现这个宏在程序中提示宏未定义的情况在编写大型程序时特别需要注意因为有很多程序员在同时写代码。相反由于program once 针对整个文件 因此它不存在宏名字“撞车”的情况 但是如果某个头文件被多次拷贝program once 无法保证不被多次包含因为program once 是从物理上判断是不是同一个头文件而不是从内容上。12. 当i是一个整数的时候i和i那个更快一点i和i的区别是什么答理论上i更快实际与编译器优化有关通常几乎无差别。//i实现代码为
int operator(int)
{int temp *this;*this;return temp;
}//返回一个int型的对象本身// i实现代码为
int operator()
{*this 1;return *this;
}//返回一个int型的对象引用i和i的考点比较多简单来说就是i返回的是i的值而i返回的是i1的值。也就是i是一个确定的值是一个可修改的左值如下使用cout ((i)) endl;
cout i endl;可以不停的嵌套i。 这里有很多的经典笔试题一起来观摩下int main()
{int i 1;printf(%d,%d\n, i, i); //3,3printf(%d,%d\n, i, i); //5,3printf(%d,%d\n, i, i); //6,5printf(%d,%d\n, i, i); //8,9system(pause);return 0;
}首先是函数的参数入栈顺序从右向左入栈的计算顺序也是从右往左计算的不过都是计算完以后再进行的压栈操作对于第1个printf首先执行i返回值是i这时i的值是2再次执行i返回值是i得到i3将i压入栈中此时i为3也就是压入33对于第2个printf首先执行i返回值是原来的i也就是3再执行i返回值是i依次将35压入栈中得到输出结果对于第3个printf首先执行i返回值是5再执行i返回值是6依次将56压入栈中得到输出结果对于第4个printf首先执行i返回i此时i为8再执行i返回值是8此时i为9依次将i8也就是98压入栈中得到输出结果。上面的分析也是基于VS搞的不过准确来说函数多个参数的计算顺序是未定义的(the order of evaluation of function arguments are undefined)。笔试题目的运行结果随不同的编译器而异。这里还有一个 i 的典型应用案例。mapchar, int b {{a, 1}, {b, 2}};for(auto iter b.begin(); iter ! b.end();){if(iter-first a){b.erase(iter); // 等价于 auto t iter; iter iter 1; b.erase(t);}else{iter;}}第三部分数组、指针 引用1. 指针和引用的区别相同点1). 都是地址的概念2). 都是“指向”一块内存。指针指向一块内存它的内容是所指内存的地址而引用则是某块内存的别名3). 引用在内部实现其实是借助指针来实现的一些场合下引用可以替代指针比如作为函数形参。不同点1). 指针是一个实体而引用(看起来这点很重要)仅是个别名2). 引用只能在定义时被初始化一次之后不可变指针可变引用“从一而终”指针可以“见异思迁”3). 引用不能为空指针可以为空4). “sizeof 引用”得到的是所指向的变量(对象)的大小而“sizeof 指针”得到的是指针本身的大小5). 指针和引用的自增()运算意义不一样6). 引用是类型安全的而指针不是 (引用比指针多了类型检查)7). 引用具有更好的可读性和实用性。2. 引用占用内存空间吗如下代码中对引用取地址其实是取的引用所对应的内存空间的地址。这个现象让人觉得引用好像并非一个实体。但是引用是占用内存空间的而且其占用的内存和指针一样因为引用的内部实现就是通过指针来完成的。比如 Type name Type* const name。int main(void)
{int a 8;int b a;int *p b; // 等价于 int *p a;*p 0;couta; //output 0return 0;
}3. 三目运算符在C中三目运算符(? :)的结果仅仅可以作为右值比如如下的做法在C编译器下是会报错的但是C中却是可以是通过的。这个进步就是通过引用来实现的因为下面的三目运算符的返回结果是一个引用然后对引用进行赋值是允许的。int main(void)
{int a 8;int b 6;(ab ? a : b) 88;couta; //output 88return 0;
}4. 指针数组和数组指针的区别数组指针是指向数组的指针而指针数组则是指该数组的元素均为指针。数组指针是指向数组的指针其本质为指针形式如下。如 int (*p)[n]p即为指向数组的指针()优先级高首先说明p是一个指针指向一个整型的一维数组这个一维数组的长度是n也可以说是p的步长。也就是说执行p1时p要跨过n个整型数据的长度。数组指针是指向数组首元素的地址的指针其本质为指针可以看成是二级指针。类型名 (*数组标识符)[数组长度]指针数组在C语言和C中数组元素全为指针的数组称为指针数组其中一维指针数组的定义形式如下。指针数组中每一个元素均为指针其本质为数组。如 int *p[n] []优先级高先与p结合成为一个数组再由int*说明这是一个整型指针数组它有n个指针类型的数组元素。这里执行p1时则p指向下一个数组元素这样赋值是错误的pa因为p是个不可知的表示只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *pa; 这里*p表示指针数组第一个元素的值a的首地址的值。类型名 *数组标识符[数组长度]5. 左值引用与右值引用该部分主要摘自c 学习笔记左值引用就是我们通常所说的引用如下所示。左值引用通常可以看作是变量的别名。type-id cast-expression // demo
int a 10
int b aint c 10 // 错误无法对一个立即数做引用const int d 10 // 正确 常引用引用常数量是ok的其等价于 const int temp 10; const int d temp右值引用是 C11 新增的特性其形式如下所示。右值引用用来绑定到右值绑定到右值以后本来会被销毁的右值的生存期会延长至与绑定到它的右值引用的生存期。type-id cast-expression // demo
int var 10; // okint a 10
int b a // 错误 a 为左值int c var // 错误var 为左值int d move(a) // ok, 通过move得到左值的右值引用在汇编层面右值引用做的事情和常引用是相同的即产生临时量来存储常量。但是唯一 一点的区别是右值引用可以进行读写操作而常引用只能进行读操作。6. 右值引用的意义右值引用支持移动语义的实现可以减少拷贝提升程序的执行效率。下面的代码是没有采用右值引用时的实现。class Stack
{
public:// 构造Stack(int size 1000) :msize(size), mtop(0){cout Stack(int) endl;mpstack new int[size];}// 析构
~Stack()
{cout lt;lt; ~Stack() lt;lt; endl;delete[]mpstack;mpstack nullptr;
}// 拷贝构造
Stack(const Stack amp;src):msize(src.msize), mtop(src.mtop)
{cout lt;lt; Stack(const Stackamp;) lt;lt; endl;mpstack new int[src.msize];for (int i 0; i lt; mtop; i) {mpstack[i] src.mpstack[i];}
}// 赋值重载
Stackamp; operator(const Stack amp;src)
{cout lt;lt; operator lt;lt; endl;if (this amp;src)return *this;delete[]mpstack;msize src.msize;mtop src.mtop;mpstack new int[src.msize];for (int i 0; i lt; mtop; i) {mpstack[i] src.mpstack[i];}return *this;
}int getSize()
{return msize;
}
private:int *mpstack;int mtop;int msize;
};Stack GetStack(Stack stack)
{Stack tmp(stack.getSize());return tmp;
}int main()
{Stack s;s GetStack(s);return 0;
}运行结果如下。Stack(int) // 构造s
Stack(int) // 构造tmp
Stack(const Stack) // tmp拷贝构造main函数栈帧上的临时对象
~Stack() // tmp析构
operator // 临时对象赋值给s
~Stack() // 临时对象析构
~Stack() // s析构执行代码的过程中调用拷贝构造将内存中的内容逐个拷贝在 C 11 中可以借助右值引用实现移动拷贝构造和移动赋值来解决这个问题。Stack(Stack src):msize(src.msize), mtop(src.mtop)
{cout Stack(Stack) endl;/*此处没有重新开辟内存拷贝数据把src的资源直接给当前对象再把src置空*/
mpstack src.mpstack;
src.mpstack nullptr;
}// 带右值引用参数的赋值运算符重载函数
Stack operator(Stack src)
{cout operator(Stack) endl;if(this amp;src)return *this;delete[]mpstack;msize src.msize;
mtop src.mtop;/*此处没有重新开辟内存拷贝数据把src的资源直接给当前对象再把src置空*/
mpstack src.mpstack;
src.mpstack nullptr;return *this;
}执行结果如下。可以看到在有拷贝构造和移动拷贝构造函数的时候优先调用了移动拷贝构造和移动赋值。在移动拷贝构造和移动赋值中直接把资源所有权进行了转移而非拷贝这就大大提高了执行效率。Stack(int) // 构造s
Stack(int) // 构造tmp
Stack(Stack) // 调用带右值引用的拷贝构造函数直接将tmp的资源给临时对象
~Stack() // tmp析构
operator(Stack) // 调用带右值引用的赋值运算符重载函数直接将临时对象资源给s
~Stack() // 临时对象析构
~Stack() // s析构右值引用可以使重载函数变得更加简洁。右值引用可以适用 const T 和 T 形式的参数。struct W
{ W(int, int) {}
}; struct X
{ X(const int, int) {}
}; struct Y
{ Y(int, const int) {}
}; struct Z
{ Z(const int, const int) {}
};template typename T, typename A1, typename A2
T* factory(A1 a1, A2 a2)
{ return new T(a1, a2);
} template typename T, typename A1, typename A2
T* factory_new(A1 a1, A2 a2)
{ return new T(std::forwardA1(a1), std::forwardA2(a2));
} // demo
int a 2;
int b 2;W* c factoryw(a, b); // ok
Z* d factoryZ(2, 2); // 错误2 是右值W* pw factory_newW(a, b); // ok
X* px factory_newX(2, b); // ok
Y* py factory_newY(a, 2); // ok
Z* e factory_newZ(2, 2); // ok
W* f factory_newW(2, 2); // 错误,更多相关内容可以参考c——左值、右值、左值引用、右值引用第四部分C特性1. 什么是面向对象OOP面向对象的意义Object Oriented Programming, 面向对象是一种对现实世界理解和抽象的方法、思想通过将需求要素转化为对象进行问题处理的一种思想。其核心思想是数据抽象、继承和动态绑定多态。 面向对象的意义在于将日常生活中习惯的思维方式引入程序设计中将需求中的概念直观的映射到解决方案中以模块为中心构建可复用的软件系统提高软件产品的可维护性和可扩展性。2. 解释下封装、继承和多态1). 封装 封装是实现面向对象程序设计的第一步封装就是将数据或函数等集合在一个个的单元中我们称之为类。 封装的意义在于保护或者防止代码数据被我们无意中破坏。 从封装的角度看public private 和 protected 属性的特点如下。和 public 一样可以被子类继承和 private 一样不能在类外被直接调用特例在衍生类中可以通过衍生类对象访问如下代码所示不管哪种属性内类都是可以访问的public 是一种暴露的手段比如暴露接口类的对象可以访问private 是一种隐藏的手段类的对象不能访问protected 成员class Base
{
public: Base(){}; virtual ~Base(){};
protected: int int_pro;
};
class A : public Base
{
public: A(){}; A(int da){int_pro da;} // 通过 obj 对象直接访问 protected 成员void Set(A obj){obj.int_pro 24;} void PrintPro(){cout The proteted data is int_pro endl;}
};2). 继承 继承主要实现重用代码节省开发时间。 子类可以继承父类的一些东西。a.公有继承(public) 公有继承的特点是基类的公有成员和保护成员作为派生类的成员时它们都保持原有的状态基类的私有成员仍然是私有的不能被这个派生类的子类所访问。b.私有继承(private) 私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员并且不能被这个派生类的子类所访问。c.保护继承(protected) 保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员并且只能被它的派生类成员函数或友元访问基类的私有成员仍然是私有的。这里特别提一下虚继承。虚继承是解决C多重继承问题其一浪费存储空间第二存在二义性问题的一种手段。比如菱形继承典型的应用就是 iostream, 其继承于 istream 和 ostream而 istream 和 ostream 又继承于 ios。3).多态 多态是指通过基类的指针或者引用在运行时动态调用实际绑定对象函数的行为。与之相对应的编译时绑定函数称为静态绑定。多态是设计模式的基础多态是框架的基础。3. 什么时候生成默认构造函数无参构造函数什么时候生成默认拷贝构造函数什么是深拷贝什么是浅拷贝默认拷贝构造函数是哪种拷贝什么时候用深拷贝1). 没有任何构造函数时编译器会自动生成默认构造函数也就是无参构造函数当类没有拷贝构造函数时会生成默认拷贝构造函数。2). 深拷贝是指拷贝后对象的逻辑状态相同而浅拷贝是指拷贝后对象的物理状态相同默认拷贝构造函数属于浅拷贝。3). 当系统中有成员指代了系统中的资源时需要深拷贝。比如指向了动态内存空间打开了外存中的文件或者使用了系统中的网络接口等。如果不进行深拷贝比如动态内存空间可能会出现多次被释放的问题。是否需要定义拷贝构造函数的原则是类是否有成员调用了系统资源如果定义拷贝构造函数一定是定义深拷贝否则没有意义。更多可以参考下面的代码比较容易混淆的是赋值操作符其实区分很简单在出现等号的时候如果有构造新的对象时调用的就是构造不然就是调用赋值操作符。特别注意下面的 b 和 f一个是拷贝构造一个是构造。class A {
public:A() {m new int[4]{ 1,2,3,4 };std::cout constructor std::endl;}~A() {if (m ! nullptr) {delete[] m;}}A(const A a) {this-m new int[4];memcpy(a.m, this-m, this-len * sizeof(int));std::cout copy constructor std::endl;}// 移动构造A(A a) : m(a.m) {a.m nullptr; std::cout move constructor std::endl;}// 赋值操作符重载A operator (const A a) {memcpy(a.m, this-m, this-len * sizeof(int));std::cout operator std::endl;return *this;}private:int len 4;int* m nullptr;
};A getA(A a) {return a;
}int main(void)
{A a; // constructA b a; // copy constructA c(a); // copy constructA d; // constructd a; // operateA e getA(a); // construct, move constructA f A(); // constructreturn 0;
}4. 构造函数和析构函数的执行顺序构造函数1). 首先调用父类的构造函数2). 调用成员变量的构造函数3). 调用类自身的构造函数。析构函数对于栈对象或者全局对象调用顺序与构造函数的调用顺序刚好相反也即后构造的先析构。对于堆对象析构顺序与delete的顺序相关。5. 虚析构函数的作用基类采用虚析构函数可以防止内存泄漏。比如下面的代码中如果基类 A 中不是虚析构函数则 B 的析构函数不会被调用因此会造成内存泄漏。class A{
public:A(){}//~A(){}virtual ~A(){cout A disconstruct endl;} // 虚析构
// ~A(){cout A disconstruct endl;} // 析构};class B : public A{
public:B(){// new memory// ...cout B construct endl;}~B(){// delete memory// ...cout B disconstruct endl;}
};int main(int argc, char **argv)
{A *p new B;// some operations// ...delete p; // 由于基类中是虚析构这里会先调用B的析构函数然后调用A的析构函数return 0;
}但并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候编译器会给类添加一个虚函数表里面来存放虚函数指针这样就会增加类的存储空间。所以只有当一个类被用来作为基类的时候才把析构函数写成虚函数。6. 细看拷贝构造函数对于 class A它的拷贝构造函数如下A::A(const A a){}1) 为什么必须是当前类的引用呢循环调用。如果拷贝构造函数的参数不是当前类的引用而是当前类的对象那么在调用拷贝构造函数时会将另外一个对象直接传递给形参这本身就是一次拷贝会再次调用拷贝构造函数然后又将一个对象直接传递给了形参将继续调用拷贝构造函数……这个过程会一直持续下去没有尽头陷入死循环。只有当参数是当前类的引用时才不会导致再次调用拷贝构造函数这不仅是逻辑上的要求也是 C 语法的要求。2) 为什么是 const 引用呢拷贝构造函数的目的是用其它对象的数据来初始化当前对象并没有期望更改其它对象的数据添加 const 限制后这个含义更加明确了。另外一个原因是添加 const 限制后可以将 const 对象和非 const 对象传递给形参了因为非 const 类型可以转换为 const 类型。如果没有 const 限制就不能将 const 对象传递给形参因为 const 类型不能直接转换为非 const 类型这就意味着不能使用 const 对象来初始化当前对象了。7. C的编译环境如下图所示C的编译环境由如下几部分构成C标准库、C语言兼容库、编译器扩展库及编译模块。 在这里插入图片描述#includeiostream //C标准库不带.h
#includestring.h //C语言兼容库由编译器厂商提供值得注意的是C语言兼容库功能上跟C标准库中的C语言子库相同它的存中主要为了兼容C语言编译器也就是说如果一个文件只包含C语言兼容库不包含C标准库那么它在C语言编译器中依然可以编译通过。8. Most vexing parse直接上代码吧。下面 f 和 g 是有问题的这种情况就称为 Most vexing parse。class A {
public:A() { cout const without param endl; }A(int a) { cout const with param endl; }A(const A b) { cout copy construct endl; }
};int main(void)
{A a; // const(construct) without paramA b(10); // const with paramA c A(); // const without paramA d A(10); // const with paramA e(d); // copy constructA f(); A g(A());A h{}; // const without paramA i{A{}}; // const without paramreturn 0;
}问题在哪A f(); // 这个是不是可以看做声明了一个返回值为A的函数函数名为 f参数无
A g(A()); // 这个是不是可以看做声明了一个返回值为A的函数函数名为 g, 参数类型为函数指针这个函数指针的返回值类型为A参数无解决办法参考上面的 h j。*声明本文于网络整理版权归原作者所有如来源信息有误或侵犯权益请联系我们删除或授权事宜。戳“阅读原文”我们一起进步