文章目录
1.C/C++程序基础
1.1 C和C++的区别
(1)C是结构化语言,偏向于面向过程,它的重点在于设计算法和数据结构。
(2)C是偏向于面向对象的语言。C++扩展了面向对象的功能,如类、继承、模板、虚函数等,不仅要考虑封装,还要考虑对象接口的设计、继承使用等问题。
(3)最大区别在于解决问题的思想不同,C考虑的是如何通过一个过程来完成任务,C++考虑的是如何构造一个对象模型,让这个模型能配合对应的问题。
(4)C++拥有面向对象的特征但是也可以实现过程化的程序,Java才是真正面向对象的。
延伸,C和C++具体区别:
1、从机制上:
C是面向过程的结构化编程语言(但也可以编写面向对象的程序),偏向于面向过程的程序设计
C++是面向对象的,提供了类。侧重于类的设计而不是过程的设计。
2、从适用的方向:
C适合代码体积小的,效率高的场合,如嵌入式;
C++适合更上层的,复杂的场合。
3、语法的细微差异:
(1)C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中
c++和c中字符串:c++是类,c中是基本类型函数。
(2)C语言的结构体只是复杂的数据类型,而C++的结构体拥有类的功能。
面向对象程序设计的主要优点
使程序设计者摆脱具体的数据格式和过程的束缚,极大减少软件开发的复杂性,提高软件开发效率。
优点:
(1)提高程序的重用性
(2)控制程序的复杂性
(3)改善程序的可维护性
(4)能更好地支持大型程序设计
(5)增强计算机处理信息的范围
(6)能更好地适应新的硬件环境
面向过程和面向对象编程区别
1)面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用
2)面向对象是把构成问题事物分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为
3)总的来说,面向对象就是高度事物抽象化,而面向过程就是自顶向下的编程
1.2 模块化程序设计
模块化程序设计:是指在进行程序设计时将一个大程序按照功能划分为若干小程序模块,每个小程序模块完成一个确定的功能,并在这些模块之间建立必要的联系,通过模块的互相协作完成整个程序功能(的程序设计方法)
模块化程序设计意义:
1)模块化程序设计的思想基本思想是自顶向下、逐步分解、分而治之。即将一个较大的程序按照功能分割成一些小模块,各模块相互独立、功能单一、结构清晰、接口简单
2)降低程序复杂度,使程序设计、调试和维护等操作简单化
3)提高了代码的重用性
4)易于维护和功能扩充
5)有利于团队开发
1.3 C++主函数前后执行的语句
主函数前执行的语句:全局对象的构造函数(构造全局自定义的 对象)。
一个程序的启动函数这个可以采用链接器来设置,但是gcc中默认main就是C语言的入口函数。
在main函数启动之前,内核会调用一个特殊的启动例程,这个启动例程从内核中取得命令行参数值和环境变量值,为调用main函数做好准备,因此对应程序而言main函数并不是起始,但是对应C 语言而言,main函数就是入口地址,其
主函数后执行的语句:可以用atexit函数来注册程序正常中止时要被调用的函数,并且在main函数结束时调用这些函数(调用顺序与注册顺序相反)
只可以注册无返回值无参数的函数。
主函数能不能有参数
可以。
第一个参数表示参数的个数
第二个参数是参数的向量,是一个指向字符串数组的指针,其中每个字符串对应一个参数。
第三个参数是环境变量。
1.4 常用的循环结构:for,while,dowhile
for:知道循环次数,先判断后循环
while:不知道循环次数,先判断后循环
dowhile:不知道循环次数。先循环后判断
for循环和while循环是否任何时候都可以互相转化?
可以。
不过for循环适用于已知循环次数,while循环适用于未知循环次数的时候
1.5 i++和++i的效率区别(后缀自增运算符与前缀自增运算符)
内建数据类型的情况,效率没有区别。
自定义数据类型的情况,++i效率较高。
当处理自定义数据类型(类、结构体)时,尽量使用前缀自增运算符,因为(++i)可以返回对象的引用,而后缀(i++)必须返回对象的值
后缀运算符:先对变量运算后自增。
符号争夺优先级(*与++、–同一级,右结合律,从右向左)
cout<<(*p)++ 先输出*p,后(*p)值+1
cout<<*p++ 先输出*p,再p++
cout<<*(p++) 等同于*p++,(两个都可以理解为,先p++,然后*p ---> ++函数中return原来的p )
1.6 不使用临时变量替换a与b的值
a=a+b;
b=a-b;
a=a-b;
1.7 if…else和switch区别
总结:都是条件选中语句。但switch语句只能取代if语句的一部分功能。
(1)比较的范围不同:
if 语句可做各种关系比较(只要是 boolean 表达式都可以用 if 判断)
switch语句只能做等式比较,即只能对基本类型进行数值比较。 (switch只能做几个数据类型的等式比较,实现非等式效率低,)
switch之后括号内的表达式只能是整型(byte、short、char和int)、枚举型或字符型表达式,不能是长整型或其他任何类型。
(2)效率不同:
if 语句每次判断都要读写寄存器一次,判断多效率低。
switch 一次性读取要判断的值,判断多效率高
1.8以分号结束和不以分数结束的语句
以分号结束:
结构体定义加分号
C++强制规定类在定义和声明时,结尾必来须加分号.
不加分号:
预编译指令
函数定义
2.预处理、const、static和sizeof
const作用
(1)定义常量(在程序数据段-静态区中)
(2)修饰引用传参(自定义数据类型,防止引用对象不小心改变了对象)
(3)修饰函数返回值(返回值在静态区),则返回值只能赋值给同为const的变量
(4)修饰成员函数(成员函数后加 const),表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变。
规则:const离谁近,谁就不能被修改;
2.2 include预编译指令:将被包含的头文件插入到该编译指令的位置。
(这个过程是递归进行的,因为被包含的文件可能还包含了其他文件)
include头文件<>和""区别
<>表明文件是一个工程或标准头文件,查找过程将首先检查 预定义目录。
双引号表明文件是用户提供的头文件,查找时在当前目录下找,然后再在标准文件寻找。
条件编译与头文件保护
条件编译:有时候希望只对其中一部分内容进行编译.此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃。
头文件保护:头文件中允许包含其它的头文件,为避免头文件被重复包含,可在其中使用条件编译。
2.3 static作用、类静态成员和方法
static
(1)静态全局变量作用域只在定义该变量的源文件。全局变量作用域是整个源程序(可能为多个源文件)(作用域不同·)
(2)静态局部变量只能被其作用域内的变量和函数进行访问使用,数据存放在全局数据区,只初始化一次。(存储方式不同)
(3)静态函数作用域仅在本文件(静态函数在内存只有一份,代码区?),非静态函数每个调用保持一份复制品。
(4)类的静态成员和方法不属于类的实例,而属于类本身,并在所有类的实例间共享。(不含this指针,静态成员函数无法调用非静态数据成员)
类中不可以初始化,只能在类外全局作用域初始化(默认为0),静态成员可以独立访问,无需依赖任何对象的建立(通过域操作符)
静态数据成员比全局变量的优势:(1)静态数据成员没进入全局区,所以不会和其他全局变量名字冲突(2)静态数据成员可以用private隐藏信息,全局变量不行。
2.4 sizeof和strlen区别
(1)sizeof是操作符、strlen是函数
(2)sizeof参数可以是类型,str参数只能是字符指针且必须以结束符‘\0’作结尾。
(3)sizeof是计算对象分配内存空间的大小,strlen是从某个位置开始计数,到结束符停止返回计数值。
2.5 typedef和define区别
typedeff是关键字,对已经存在的数据类型取别名。
在编译阶段处理,会进行类型检查,只能在定义的作用域内使用。
define是预处理指令(宏定义),只进行简单的字符替换,是否产生错误要在编译时才可知。
没有作用域限制,可以对类型/变量/常量等进行替换
2.6 内联函数:替代宏定义来解决函数调用的效率问题
(1)inline定义的类的内联函数,代码被放入符号表中,在使用时直接进行替换(像宏一样展开),没有了调用的开销,提升了效率。
(2)类的内联函数是一个真正的函数,调用内联函数时会进行一系列的相关检查,消除了隐患。
(3)inline可以作为某个类的成员函数,从而使用类的成员(保护私有公有)
为什么函数不全部替换成内联函数?
内联是以代码膨胀(复制)为代价的,仅仅省去了调用的开销,执行函数体内代码比较长(或内联中有循环)就会消耗内存,失去意义。
内联函数与宏定义区别?
(1)宏定义在预编译时展开,内联函数在编译时展开。
(2)编译时内联可被镶嵌到目标代码中,宏定义只是字符替换。
(3)内联函数有一系列的安全性检查
(4)内联函数是函数
(5)宏容易出现二义性
3.引用和指针
基本数据类型的特点:直接存储在栈(stack)中的数据
3.1 引用和指针区别
1)引用需要初始化,指针不需要(可以只声明不初始化)。
不存在指向空值的引用,但存在指向空值的指针。
2)引用初始化之后不能被改变(所引用的变量不能改变),指针可以改变所指的对象
引用:引用一个现有的变量来声明一个别名变量
3.2 什么是野指针
野指针不是NULL指针,而是指向垃圾内存的指针。
成因主要为:指针变量没有被初始化,或指针p被free或delete之后,没有置为NULL
3.3 为什么有了malloc/free,还要有new/delete
对于非内部数据类型的对象而言,对象在消亡之前要自动执行析构函数。
由于malloc/free是库函数,不在编译器控制权限之内,不能把执行构造函数和析构函数的任务强加于malloc/free,因此只有使用new/delete运算符。
3.4 内存分配/释放函数及区别:malloc、calloc、realloc、free等
1、malloc:一块内存,一块长度为size的内存,返回首地址,内存里面值没有初始化,是随机数。
calloc是分配n块内存,n块长度为size的内存,返回首地址,内存值初始化为0
2、realloc将原有内存大小增加到size大小,新增内存未初始化
3、free:释放ptr(内存空间指针)所指向的内存空间
new、free可以调用类的构造函数和析构函数。
4.字符串
4.1 strcpy和memcopy区别
strpy:只能复制字符串,不需要指定长度,遇到结束符停止
memorycopy:可以复制任何内容,需要一个参数来指定长度.
6.面向对象
6.1 类的特性:封装、继承、多态(面向对象三大特征)、抽象、重载
1、封装:隐藏对象的属性和实现细节,仅对外公开接口 。
(1)将抽象得到的属性和(属性上的)方法相结合,形成一个有机的整体,形成“类”,其中数据和函数都是类的成员。
(2)封装使得代码模块化
2、继承 :使得子类具有父类的属性和方法或者重新定义、追加属性和方法等
(1)继承是使代码可以复用的重要手段,也是面向对象程序设计的核心思想之一。
(2)继承就是不修改原有的类,直接利用原来的类的属性和方法并进行扩展。
原来的类称为基类,继承的类称为派生类,他们的关系就像父子一样,所以又叫父类和子类。
类和对象区别
1)类是事物的描述和抽象,是具有相同属性和行为的对象集合,类是用于创建对象的蓝图。
对象是类的实例,是具体存在的事物,明确定义状态和行为
2)类是抽象的,不占用内存,而对象是具体的,占有存储空间,
6.2 struct和clas区别
1、C语言struct:
只是一个复杂的数据结构,定义成员变量时不可以初始化值,而且不可以定义成员函数。结构体不能为空
2、C++ struct:
可以实现面向对象,成员默认访问权限和继承方式为public,不可以用于表示模板类型。
(C++中保留struct关键字是为了使C++编译器能够兼容C语言开发的程序)
3、C++ Class:成员访问权限和继承方式默认为private,可以用于表示模板类型。
6.3 C++空类中默认产生哪些成员函数:默认构造函数、复制构造函数、析构函数、赋值函数和取址函数
6.4 简要说明构造函数与析构函数
构造、析构函数的名称与类的名称相同。为了区分,析构函数名字前面有~
都无返回值,都不能被直接调用 (像调用其他成员函数一样)
1、构造函数:用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。
(1)构造函数可以重载,因为构造函数可以有多个,且可以带参数。
(2)调用不带参数的构造函数不需要小括号
(3)定义了自定义构造函数后,编译器是不会创建默认无参构造函数的
(4)初始化类时自动调用构造函数,可通过new调用
struct Test
{Test(){cout<<"调用无参构造函数";}};
int main()
{Test a;//无参不加括号}
构造函数:完成与构造函数相反的工作,对象退出生命周期时,完成清理的工作如释放内存
(1)不可重载,只能有一个,而且不能有参数
(2)当类中有动态内存分配时,需要增加自定义的析构函数,否则有可能导致内存泄漏。
(3)对象退出作用域时自动调用析构函数,释放对象,可通过delete调用
复制构造函数:特殊的构造函数,完成同一类其他对象的初始化
如果用户没有定义复制构造函数,并且在代码中用到了复制构造函数,编译器会生成默认的复制构造函数。
但如果用户定义了复制构造函数,那么编译器不会再生成复制构造函数。
深拷贝与浅拷贝区别
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。(也就是对象)
浅复制:新旧对象指向同一个外部内容。
只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
深复制:为新对象制造了外部对象的独立复制。
会另外创造一个一模一样的对象,新对象跟原对象不共享内存。
初始化列表:可在构造函数初始化列表初始化成员
1、使用情况
当类中含有const、reference成员变量和基类的构造函数时都需要初始化列表
const、reference只能被初始化不能被赋值,基类的构造函数也不能直接调用。
2、构造顺序
初始化内置数据类型(自定义类型也是):构造顺序与变量声明顺序一致
struct Test
{
Test(int k):j(k),i(j){}
//先执行i=j(随机数),再执行j=k=2;
private:
int i; //先声明的i
int j;
};
虚析构函数:当一个类作为基类时,虚析构函数会先执行子类虚构函数
防止内存泄漏。
6.5 复制构造函数和赋值函数区别
(1)复制构造函数是用一个对象来初始化一个内存空间,这块内存就是新对象的内存。
(2)复制构造函数首先是一个构造函数,通过参数传递的对象来初始化产生一个对象。
赋值函数是把一个对象赋值给一个原有的对象(先把原有对象内存释放)
(3)对数据成员为指针对象的操作,一是复制(指针对象),二是引用。
复制构造函数大多都是复制,赋值函数是引用。
6.6 什么是内存泄漏
new创建的对象,没有delete。
6.7 访问修饰符与继承方式
1、公有(public)成员:在程序中类的外部是可访问的。可以不使用任何成员函数来设置和获取公有变量的值
2、保护(protected)成员:只能被类成员函数、友元访问和派生类(成员函数)访问,派生类对象也不能(在类外)访问
3、私有(private)成员:只能被本类成员(类内)和友元访问,不能被派生类访问。默认情况下,类的所有成员都是私有的。
继承方式:
1.public 继承:基类 成员访问属性在派生类中保持不变。
2.protected 继承:基类 public 成员在派生类中变成protected成员,其他不变。
3.private 继承:基类成员访问属性在派生类中都变为private属性。
7.多态和继承
7.1 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同类型的执行结果
有两种类型的多态性
(1)编译时的多态性:编译时的多态性通过重载来实现,对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。
(2)运行的多态性:运行的多态性通过虚成员,直到运行时才根据实际情况确定实现何种操作。
虚函数:允许用基类的指针来调用 子类对应的虚函数实现
是C++中用于实现多态的机制,核心理念就是通过基类访问派生类定义的函数。
一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由多态方式调用的时候(通过指针)动态绑定。
1、虚函数可以被直接使用(动态多态,由子类实现覆盖,使用指针)。
2、虚函数只能借助于指针或者引用来达到多态的效果。
3、虚函数和纯虚函数的定义中不能有static标识符
4、虚函数是严格的一对一关系(父类 子类的一对同名同参数 函数)
子类的函数与父类虚函数若同名但不同参,则毫无关系。属于对继承的父类虚函数的一种重载。
纯虚函数与抽象类:含有纯虚函数的类被称为抽象类
纯虚函数:在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。
在基类中实现纯虚函数的方法是在函数原型后加 =0:
virtual void funtion()=0
含有纯虚函数的类被称为抽象类(abstract class)
纯虚函数是特殊的虚函数,特殊在于不可以直接调用,必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。
重载:同一作用域中多个同名函数,形参列表必须不同
覆盖(重写):覆盖基类虚函数(参数必须相同)
覆盖(也叫重写):是指在派生类中重新对基类中的虚函数重新实现。
函数名和参数必须一样,返回值可以不一样(允许返回值协变)。
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
如果参数不同,即使有virtual也算作隐藏。
隐藏与覆盖的区别
函数本身有(代码区)对应的地址
(1)重载是子类改写了父类的方法,另辟空间,父类原函数还在。
方法名必须相同,参数必须不相同,(返回类型可以不相同)
(2)覆盖是派生类重写基类的虚函数,父类原函数被替换
(3)重载不属于面向对象,只是一种语法规则,在编译阶段完成。
(4)覆盖是面向对象的特征,在运行时决定。
7.2 抽象类(概念,并有具体对应)
1、抽象类至少包含一个纯虚函数,子类只有实现所有纯虚函数才不是抽象类。
2、抽象类不能直接实例化。
3、抽象类可以有非纯虚函数。
4、可以支持多继承
接口(概念,无具体对应。对应 仅纯虚函数的抽象类)
C++中并没有明确的接口的定义,与之等价的是纯虚类,既只有纯虚函数的(抽象)类。
1、接口不包含方法的实现,全部都为纯虚函数
2、接口的子类必须实现其所有方法
3、接口是抽象方法的集合,不能被实例化;
4、可以支持多继承
5、通常不带成员变量,也没有构造函数。
接口不能包含成员变量,除了 static 和 final 变量。
7.3 多继承:一个类有多个基类,这样的继承关系称为多继承
多继承重复调用同一基类构造函数造成二义性:
根据多继承构造函数的调用顺序,先构造Base类对象再被继承为a类对象,
然后又先构造Base类对象再被继承为b类对象。
可见对于C类对象来说,Base构造了两次,所以Base中的任何属性都具有二义性。
分析C的对象模型:
虚继承解决多继承的二义性问题
如果要让Base在C中只产生一个对象,则应该对公共基类Base声明为虚继承,使得这个公共基类成为虚基类。
当类中出现virtual时,C++编译器会对象添加一个vptr指针,同时会产生一个虚函数表
//当类中出现virtual时,C++编译器会对象添加一个vptr指针,同时会产生一个虚函数表
class Base{};
class A: virtual public Base{};
class B : virtual public Base{};
class C :public A,public B{};
C的对象模型:
实际上A应该包括a和vptr指向的x,共12字节。B一样
7.4 接口继承和实现继承
所谓接口继承就是派生类只继承函数的接口,也就是声明。而实现继承就是派生类同时继承函数的接口和实现。
1、声明一个纯虚函数(pure visual)的目的就是为了让派生类只继承函数接口,即接口继承。
2、声明一个非纯虚函数(impure visual)的目的是为了让派生类继承函数接口和缺省实现。
3、声明一个非虚函数(non visual)的目的是为了让派生类继承函数接口和一份强制实现。
7.5 this指针的含义:指向成员函数作用的对象
“this指针”指向了成员函数作用的对象,在成员函数执行的过程中,正是通过“this指针”才能找到对象所在的地址,因而也就能找到对象的所有非静态成员变量的地址。
在类中构造函数创造一个对象。
7.6 类对象与类指针:
两者不可相提并论,类对象不可理解成指针,虽然实现机制很像。
1、类对象:利用类的构造函数(构造函数:对类进行初始化工作)在内存中分配的一块区域(包括一些成员变量赋值);
2、类指针:一个内存地址值,指向内存中存放的类对象(包括一些成员变量所赋的值).
要发挥虚函数的强大作用,必须使用指针来访问对象. 指针可以实现动态多态,直接用对象不行
区别与特征:
(1)在类的声明尚未完成的情况下,可以声明指向该类的指针,但是不可声明该类的对象…
(2)父类的指针可以指向子类的对象…
(3)定义对象实例时,分配了内存。指针变量则未分配类对象所需内存
(4)指针变量是间接访问,但可实现多态(通过父类指针可调用子类对象),并且没有调用构造函数。
直接声明可直接访问,但不能实现多态,声明即调用了构造函数(已分配了内存)。
8.数据结构
8.1 两个栈实现队列
栈A入栈B:栈A提供入队列,栈B提供出队列。
(1)栈A不空,直接弹出
(2)栈B空,弹A数据如B
10.泛型编程:多种数据类型皆可操作(模板实现)
10.1 函数模板与类模板
函数模板:是一个抽象的函数定义,它代表一类同构函数。
通过用户提供的具体参数,C++编译器在编译时刻能够将模板函数实例化,根据同一模板创建出不同的具体函数。
这些函数的不同之处主要在于内部一些数据类型的不同。
函数模板用于生产函数,是一个模板。
模板函数是由一个模板生产的函数。
template<模板参数表>函数定义
template<class T,typename Y> //class此处不代表类而是类型(可用typeame替换)
T max(T a,T b)
{
return a>b?a:b;
}
int main(){
cout<<max(2,5);
cout<<max(2.3,5.4);
cout<<<int>max(2,5); //指定是int
}
类模板:更高层次的抽象的类定义。
实例化必须由程序员在程序中显式地指定
类模板用于生产类
模板类是由一个模板生产的类。
template<模板参数表>类定义
//假设类名max
max<int,int> m1; //必须指定实例化,m1是模板类的对象
10.2 模板缺点
模板函数缺点:不正当使用会导致代码膨胀,严重影响程序运行效率;
解决办法:把C++模板中与参数无关的代码分离出来。