本文共 9722 字,大约阅读时间需要 32 分钟。
文章目录
1.面向对象
C++是面向对象的编程语言。面向对象的程序设计开发时间短,效率高, 可靠性高。面向对象编程的编码具有高可重用性,可以在应用程序中大量采用成熟的类库(如STL),从而虽短了开发时间,软件易于维护和升级。
- 面向对象的基本概念:类、对象和继承。所考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题,通过获取对象的状态信息得到输出或实现过程控制。
- 面向对象的基本特征/三大特性:封装、继承和多态。 封装:将低层次的元素组合起来形成新的、更高实体的技术; 继承:广义的继承有三种实现形式:实现继承、可视继承、接口继承。 多态:允许将子类类型的指针赋值给父类类型的指针
类和对象
把一些具有共性的对象归类后形成一个集合,也就是所谓的类。 类是对象的抽象,对象是类的实例。
- C++空类默认有哪些成员函数? 默认构造函数、析构函数、复制/拷贝构造函数、赋值函数
- 一个类的构造函数和析构函数什么时候被调用,是否需要手工调用? 答:构造函数在创建类对象的时候被自动调用,析构函数在类对象生命期结束时,由系统自动调用。
- 在头文件中进行类的声明,在对应的实现文件中进行类的定义有什么意义 这样可以提高编译效率,因为分开的话只需要编译一次生成对应的.obj文件后,再次应用该类的地方,这个类就不会被再次编译,从而大大的提高了编译效率。
- 在类的内部定义成员函数的函数体,这种函数会具备那种属性 种函数会自动为内联函数,这种函数在函数调用的地方在编译阶段都会进行代码替换。
- 成员函数通过什么来区分不同对象的成员数据?为什么它能够区分? 通过this指针指向对象的首地址来区分的。
- 拷贝构造函数在哪几种情况下会被调用? a.当类的一个对象去初始化该类的另一个对象时; b.如果函数的形参是类的对象,调用函数进行形参和实参结合时; c.如果函数的返回值是类对象,函数调用完成返回时。
- 构造函数与普通函数相比在形式上有什么不同? 构造函数是类的一种特殊成员函数,一般情况下,它是专门用来初始化对象成员变量的。 构造函数的名字必须与类名相同,它不具有任何类型,不返回任何值。
- 什么时候必须重写拷贝构造函数? 当构造函数涉及到动态存储分配空间时,要自己写拷贝构造函数,并且要深拷贝。
- 构造函数的调用顺序是什么? a.先调用基类构造函数 b.按声明顺序初始化数据成员 c.最后调用自己的构造函数。
- 哪几种情况必须用到初始化成员列表? a.类的成员是常量成员初始化; b.类的成员是对象成员初始化,而该对象没有无参构造函数。 c.类的成员为引用时。
- 静态函数存在的意义? 静态私有成员在类外不能被访问,可通过类的静态成员函数来访问; 当类的构造函数是私有的时,不像普通类那样实例化自己,只能通过静态成员函数来调用构造函数。
- 如何定义和实现一个类的成员函数为回调函数? 答:所谓的回调函数,就是预先在系统的对函数进行注册,让系统知道这个函数的存在,以后,当某个事件发生时,再调用这个函数对事件进行响应。 定义一个类的成员函数时在该函数前加CALLBACK即将其定义为回调函数,函数的实现和普通成员函数没有区别
继承
- 公有继承、受保护继承、私有继承 答:a、公有继承时,派生类对象可以访问基类中的公有成员,派生类的成员函数可以访问基类中的公有和受保护成员; b、私有继承时,基类的成员只能被直接派生类的成员访问,无法再往下继承; c、保护继承时,基类的成员也只被直接派生类的成员访问,无法再往下继承。 d、公有继承时基类受保护的成员,可以通过派生类对象访问但不能修改。
- 继承的优缺点
- 优点: a、类继承是在编译时刻静态定义的,且可直接使用, b、类继承可以较方便地改变父类的实现。
- 缺点: a、因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现 b、父类通常至少定义了子类的部分行为,父类的任何改变都可能影响子类的行为 c、如继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。
封装
多态
多态就是将基类类型的指针或者引用指向派生类型的对象。多态通过虚函数机制实现。
- 程序的功能是在编译的时候就确定下来的, 我们称之为静态特性。
- 程序的功能是在运行时刻才能确定下来的, 则称之为动态特性。
- 在程序运行时多态通过继承和虚函数来体现;
- 在程序编译时多态体现在函数和运算符的重载上;
多态的作用主要是两个:a. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;
b. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用2.内存管理
a、 静态存储区,是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变量、常量。
b、 栈上分配,函数内的局部变量就是从这分配的,但分配的内存容易有限。 c、 堆上分配,也称动态分配,如我们用new,malloc分配内存,用delete,free来释放的内存。数据段 | 存放内容 | 特点 |
---|---|---|
BSS | 程序中未初始化的全局变量和静态变量 | 可读写,在程序执行之前BSS段会自动清0。所以未初始的全局变量在程序执行之前已经成0了。 |
DATA数据段 | 程序中已初始化的全局变量 | 数据段属于静态内存分配。 |
TEXT代码段 | 程序执行代码 | 区域大小在程序运行前就已经确定,通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中也有可能包含一些只读的常数变量,例如字符串常量等。 |
HEAP堆 | 进程运行中被动态分配的内存段 | 大小不固定,可动态扩张或缩减。malloc/new(堆被扩张)free/delete(堆被缩减) |
STACK栈/堆栈 | 程序临时创建的局部变量 | 函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。堆栈看成一个寄存、交换临时数据的内存区 |
- 堆和栈的区别 a、申请方式不同。栈上有系统自动分配和释放;堆上有程序员自己申请并指明大小; b、栈是向低地址扩展的数据结构,大小很有限;堆是向高地址扩展,是不连续的内存区域,空间相对大且灵活; c、栈由系统分配和释放速度快;堆由程序员控制,一般较慢,且容易产生碎片;
- 全局变量和局部变量有什么区别?怎么实现的?操作系统和编译器是怎么知道的? a、生命周期不同: 全局变量随主程序创建和创建,随主程序销毁而销毁 局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在;内存中 分配在全局数据区 b、使用方式不同:通过声明后全局变量程序的各个部分都可以用到;局部变量只能在局部使用,分配在栈区 操作系统和编译器通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在堆栈里面。
- 局部变量和全局变量是否可以同名? 能。局部会屏蔽全局。要用全局变量,需要使用”::”(域运算符)。
- static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别? static全局变量与普通全局变量区别:static全局变量只初使化一次,防止在其他文件单元中被引用; static局部变量和普通局部变量区别:static局部变量只被初始化一次,下一次依据上一次结果值;
3.进程与线程
进程是一段程序在一个资源集合上的一次执行过程,是操作系统资源分配的基本单位。
线程是进程中的一个执行,他不拥有资源,是操作系统调度执行的最小单位。- 进程与线程的区别 a、进程是程序的一次执行,线程是进程中的执行单元; b、进程间是独立的,这表现在内存空间、上下文环境上,线程运行在进程中; c、一般来讲,进程无法突破进程边界存取其他进程内的存储空间;而同一进程所产生的线程共享内存空间; d、同一进程中的两段代码不能同时执行,除非引入多线程。
- 什么时候使用多线程,什么时候使用多进程 进程创建和撤销的开销比较大,每次创建和取消都要分配一个进程控制块,比如频繁建立连接,使用多线程;cpu频繁切换使用多线程。多线程适用于多核处理机。 但是多进程可以使用在多机分布式系统,需要扩展到其他机器上,使用多进程。 那如果有很多任务,创建线程越多越好吗? 不是,创建线程越多,对共享资源同步要求多,设计同步容易出错。另外多线程并发容易导致资源分配问题 有没有优化的方式呢?很多线程任务,怎么解决呢 线程池
- 进程间如何通信 信号、信号量、消息队列、共享内存
- 进程状态 就绪:当一个进程获得了除了CPU以外的所有资源,处于就绪状态 运行:获得CPU以后,任务调度,转换到执行状态,如果时间片用完,又会回到就绪状态 阻塞:处于执行过程中的进程,由于I/O请求,变到阻塞状态
- 进程同步方式 1、临界区(Critical Section):通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。 优点:保证在某一时刻只有一个线程能访问数据的简便办法 缺点:虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。 2、互斥量(Mutex):为协调共同对一个共享资源的单独访问而设计的。 互斥量跟临界区很相似,比临界区复杂,互斥对象只有一个,只有拥有互斥对象的线程才具有访问资源的权限。 优点:使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。 缺点:①互斥量是可以命名的,也就是说它可以跨越进程使用,所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。 ②通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号量对象可以说是一种资源计数器。 3、信号量(Semaphore):为控制一个具有有限数量用户资源而设计。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。互斥量是信号量的一种特殊情况,当信号量的最大资源数=1就是互斥量了。 优点:适用于对Socket(套接字)程序中线程的同步。 缺点:①信号量机制必须有公共内存,不能用于分布式操作系统,这是它最大的弱点; 4、事件(Event): 用来通知线程有一些事件已发生,从而启动后继任务的开始。 优点:事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步操作
总结:
①临界区不是内核对象,只能用于进程内部的线程同步,是用户方式的同步。互斥、信号量是内核对象可以用于不同进程之间的线程同步(跨进程同步)。 ②互斥其实是信号量的一种特殊形式。互斥可以保证在某一时刻只有一个线程可以拥有临界资源。信号量可以保证在某一时刻有指定数目的线程可以拥有临界资源。- 进程调度算法 先来先服务、短作业(进程)优先、优先权调度算法、高响应比优先、基于时间片的轮转调度算法
- 如何保证线程安全 乐观锁,悲观锁
- 线程独有的内容包括 线程ID 寄存器组的值 线程堆栈 错误的返回码 线程的信号屏蔽码
- 线程共享内容包括 进程代码段 进程的公有数据 进程打开的文件描述符 信号处理器 进程用户ID和进程组ID
一个进程可创建一个或多个线程,一个线程(主线程)可创建一个或多个线程。进程有独立的地址空间,线程没有。线程与线程通信不可使用共享内存。
-
线程之间共享数据的方式
-
死锁是什么
死锁是指多个进程因抢占资源而发生的一种阻塞且相互等待的现象 ,死锁的产生源于系统资源不足和进程推进顺序不当 ,可以通过终止和撤销进程来解除死锁 。
4.指针和引用
-
什么是指针?
指针是一个变量,该变量专门存放内存地址; 指针变量的类型取决于其指向的数据类型,在所指数据类型前加* 指针变量的特点是它可以访问所指向的内存。 -
什么是常指针,什么是指向常变量的指针?
常指针的含义是该指针所指向的地址不能变,但该地址所指向的内容可以变化,使用常指针可以保证我们的指针不能指向其它的变量。 指向常变量的指针是指该指针的变量本身的地址可以变化,可以指向其它的变量,但是它所指的内容不可以被修改。 -
函数指针和指针函数的区别?
函数指针是指向一个函数入口的指针; 指针函数是函数的返回值是一个指针类型。 int *p[n];—–指针数组,每个元素均为指向整型数据的指针。 int (*)p[n];—p为指向一维数组的指针,这个一维数组有n个整型数据。 int *p();——函数带回指针,指针指向返回的值。 int (*)p();—-p为指向函数的指针。 -
指针和引用的区别?
指针和引用都提供了间接操作对象的功能。 a、 指针定义时可以不初始化,而引用在定义时就要初始化。和一个对象绑定,而且一经绑定,只要引用存在,就会一直保持和该对象的绑定; b、引用初始化以后不能被改变,指针可以改变所指的对象。 c、不存在指向空值的引用,但是存在指向空值的指针。 d、 赋值行为的差异:指针赋值是将指针重新指向另外一个对象,而引用赋值则是修改对象本身; e、 指针之间存在类型转换,而引用分const引用和非const应用,非const引用只能和同类型的对象绑定,const引用可以绑定到不同但相关类型的对象或者右值 -
空指针和悬垂指针的区别?
答:空指针是指被赋值为NULL的指针;delete指向动态分配对象的指针将会产生悬垂指针。 a、 空指针可以被多次delete,而悬垂指针再次删除时程序会变得非常不稳定; b、 使用空指针和悬垂指针都是非法的,而且有可能造成程序崩溃,如果指针是空指针,尽管同样是崩溃,但和悬垂指针相比是一种可预料的崩溃。 -
什么是智能指针?
当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。 智能指针的一种通用实现技术是使用引用计数。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。 每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。 -
什么是“引用”?申明和使用“引用”要注意哪些问题?
引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。 申明一个引用的时候,切记要对其进行初始化。 引用声明完毕后,不能再把该引用名作为其他变量名的别名。 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名。 引用本身不是一种数据类型,因此引用不占存储单元,系统也不给引用分配存储单元。 不能建立数组的引用。 -
将“引用”作为函数参数有哪些特点?
a、传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。 b、使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。 c、使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。 -
将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 } 好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error! 注意事项: a、不能返回局部变量的引用。 b、不能返回函数内部new分配的内存的引用 c、可以返回类成员的引用,但最好是const。
5.函数
- C++函数中值的传递方式有哪几种? 三种传递方式为:值传递、指针传递和引用传递。
- 内联函数在编译时是否做参数类型检查? 内联函数要做参数类型检查, 这是内联函数跟宏相比的优势。
- 虚函数是怎么实现的? 简单说来使用了虚函数表.
- 虚拟函数与普通成员函数的区别?内联函数和构造函数能否为虚拟函数? 区别:虚拟函数有virtual关键字,有虚拟指针和虚函数表,虚拟指针就是虚拟函数的接口,而普通成员函数没有。内联函数和构造函数不能为虚拟函数。
- 析构函数为什么要虚拟? 析构函数虚拟是为了防止析构不彻底,造成内存的泄漏。
- 函数参数怎么入栈?为什么会这样?
- static函数与普通函数有什么区别? static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
6.重载和重写(覆盖)
重载
- 运算符重载的三种方式? 普通函数,友元函数,类成员函数。
- 不允许重载的5个运算符是哪些? .*成员指针访问运算符号 ::域运算符 Sizeof 长度运算符号 ?:条件运算符号 .成员访问符
函数重载是什么意思?它与虚函数的概念有什么区别?
函数重载是一个同名函数完成不同的功能,编译系统在编译阶段通过函数参数个数、参数类型不同,函数的返回值来区分该调用哪一个函数,即实现的是静态的多态性。但是记住:不能仅仅通过函数返回值不同来实现函数重载。而虚函数实现的是在基类中通过使用关键字virtual来申明一个函数为虚函数,含义就是该函数的功能可能在将来的派生类中定义或者在基类的基础之上进行扩展,系统只能在运行阶段才能动态决定该调用哪一个函数,所以实现的是动态的多态性。它体现的是一个纵向的概念,也即在基类和派生类间实现。重写
function不想被重写, 怎么办?
区别
虚函数是基类希望派生类重新定义的函数,派生类重新定义基类虚函数的做法叫做覆盖;
重载就在允许在相同作用域中存在多个同名的函数,这些函数的参数表不同。重载的概念不属于面向对象编程,编译器根据函数不同的形参表对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。 重载的确定是在编译时确定,是静态的;虚函数则是在运行时动态确定。C++是不是类型安全的?
不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)6.关键字
-
总结const的应用和作用?
a、欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了 b、对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const; c、在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值; d、对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量; e、对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。 -
总结static的应用和作用?
答:a、函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值; b、在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问; c、在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内; d、在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝; e、在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。 -
static数据成员和static成员函数
(1)static数据成员: static数据成员独立于该类的任意对象而存在;每个static数据成员是与类关联的对象,并不与该类的对象相关联。Static数据成员(const static数据成员除外)必须在类定义体的外部定义。不像普通数据成员,static成员不是通过类的构造函数进行初始化,而是应该在定义时进行初始化。 (2)static成员函数: Static成员函数没有this形参,它可以直接访问所属类的static成员,不能直接使用非static成员。因为static成员不是任何对象的组成部分。同时,static成员函数也不能被声明为虚函数。 static成员变量定义放在cpp文件中,不能放在初始化列表中。Const static成员可就地初始化。 -
STL的核心以及vector的实现(内存扩展)
-
struct和class的区别
struct 的成员默认是公有的,而类的成员默认是私有的。 -
关于sizeof小结
sizeof计算的是在栈中分配的内存大小。 a、 sizeof不计算static变量占得内存; b、 指针的大小一定是4个字节,而不管是什么类型的指针; c、 char型占1个字节,int占4个字节,short int占2个字节 long int占4个字节,float占4字节,double占8字节,string占4字节 一个空类占1个字节,单一继承的空类占1个字节,虚继承涉及到虚指针所以占4个字节 d、数组的长度: 若指定了数组长度,则不看元素个数,总字节数=数组长度*sizeof(元素类型) 若没有指定长度,则按实际元素个数类确定(若是字符数组,则应考虑末尾的空字符) e、结构体对象的长度: 在默认情况下,为方便对结构体内元素的访问和管理,当结构体内元素长度小于处理器位数的时候,便以结构体内最长的数据元素的长度为对齐单位,即为其整数倍。若结构体内元素长度大于处理器位数则以处理器位数为单位对齐。 f、unsigned影响的只是最高位的意义,数据长度不会改变,所以sizeof(unsigned int)=4 g、自定义类型的sizeof取值等于它的类型原型取sizeof h、对函数使用sizeof,在编译阶段会被函数的返回值的类型代替 i、 sizeof后如果是类型名则必须加括号,如果是变量名可以不加括号,这是因为sizeof是运算符 j、 当使用结构类型或者变量时,sizeof返回实际的大小。当使用静态数组时返回数组的全部大小,sizeof不能返回动态数组或者外部数组的尺寸 -
sizeof与strlen的区别?
a、sizeof的返回值类型为size_t(unsigned int); b、sizeof是运算符,而strlen是函数; c、sizeof可以用类型做参数,其参数可以是任意类型的或者是变量、函数,而strlen只能用char*做参数,且必须是以’\0’结尾; d、数组作sizeof的参数时不会退化为指针,而传递给strlen是就退化为指针; e、sizeof是编译时的常量,而strlen要到运行时才会计算出来,且是字符串中字符的个数而不是内存大小;
转载地址:https://blog.csdn.net/weixin_42490152/article/details/99697117 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!