(1)隐藏-当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性,故使用static在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。
(2)保持变量内容的持久:存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。
(3)默认初始化为0:其实全局变量也具备这一属性,因为全局变量也存储在静态数据区,某些时候这一特点可以减少程序员的工作量。
const修饰的内容不可改变,(const数据成员,const参数, const返回值, const成员函数等),被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
const的作用:
1)限定变量为不可修改。
2)限定成员函数不可以修改任何数据成员。
3)const与指针:
const char *p //表示 指向的内容不能改变。(常量指针)
char * const p//就是将P声明为常指针,它的地址不能改变,是固定的,但是它的内容可以改变。(指针常量)
static的作用:
(1)修饰局部变量,成为静态局部变量
int a=5; //会随着函数的结束而销毁
static int a=5;//局部变量随着函数的结束而不会被销毁
static修饰局部变量时,实际改变的是变量的存储位置,原来在栈区,被修饰后放在了静态区。
(2)修饰全局变量,成为静态全局变量
被static修饰后的全局变量给我们的感觉是作用域变小了(只能在自己文件内部使用,不能在别的文件内使用),但实质是链接属性变了。
(3)修饰函数,成为静态函数
一个函数本来是具有外部连接属性的,但是被static修饰后,外部链接属性就变成了内部连接属性,只能在自己所在的源文件内部使用,不能在其他文件内部使用了。
Const的作用:
(1)const修饰常变量
int a=5;a=6;//此时a的值被修改为6。
const int a=5;// 再修改a=6就会发生错误
(2)const修饰常量静态字符串
(3)修饰函数的参数
1.防止指针修改指针指向的内容内容
我们写以下函数
void String(char* str1,const char* arr2);
这个时候我们可以在String这个函数里面修改arr1内容,但是如果修改arr2程序便会报错,此时arr2不可再被修改。
或:
2.防止指针修改指针指向的地址
void Swap(int* const p1,int* const p2);
此时p1和p2指向的地址便不可再被修改。
C中是使用宏#define定义, C++使用更好的const来定义。
1)const是有数据类型的常量,而宏常量没有;
2)有些编译器可以对const常量进行调试, 不能对宏调试。
C++中有更好的const为什么还要使用宏?
1)为了兼容以前的 C 程序
2)const无法代替宏作为卫哨来防止文件的重复包含。
指针存储的是另一个变量的地址,通过访问这个地址来修改变量。
引用只是一个别名,还是变量本身。对引用进行的任何操作就是对变量本身进行操作,因此以达到修改变量的目的。
1)引用是对象的别名;指针存储的是地址,地址指向数据
2)引用必须在创建时初始化,初始化完毕就再也不可改变,引用的指向不可更改
3)引用具有指针的效率,又具有变量使用的方便性和直观性。引用其实就是指针常量
4)指针使用时需要解引用(*),引用则不需要;
5)"sizeof引用"得到的是所指向的变量(对象)的大小;"sizeof指针"得到指针本身的大小;
new是运算符(C++),malloc()是一个库函数©;
new会调用构造函数,malloc不会;
new返回指定类型指针,malloc返回void*指针,需要强制类型转换;
new会自动计算需分配的空间,malloc不行;
new可以被重载,malloc不能。
New/delete的用法
int *pi=new int;// 在自由存储区中分配整形对象,并返回一个指向该对象的地址来初始化指针pi
int *pi=new int();//只是对指针pi指向的地址的值进行了初始化为0
int *pi=new int(1024);// 初始化为1024。
delete pi;
pi=NULL;
malloc/free的用法
int *p=(int *)malloc(int);
if(pi==NULL)
printf("Out of memory!\n");
free (p);
a.属性:new/delete是C++关键字,需编译器支持;malloc/free是库函数,需头文件支持c。
b.参数:new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算;malloc则需要显式地指出所需内存的尺寸。
c.返回类型: new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,new是符合类型安全性的操作符;malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
e. 分配失败:new内存分配失败时,会抛出bac_alloc异常;malloc分配内存失败时返回NULL。
f.自定义类型:new/delete有构造和析构的过程;malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
g.重载:C++允许重载new/delete操作符; malloc不允许重载。
h.内存区域:new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
#include
对于#include
对于#include “a.h” ,编译器从用户的工作路径开始搜索 a.h
C++ 程序中调用被 C编译器编译后的函数,要加 extern “C”?
C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。C++提供了C连接交换指定符号extern"C"来解决名字匹配问题。
静态特性:程序的功能是在编译的时候就确定下来的
动态特性:程序的功能是在运行时刻才能确定下来的,则称之为动态特性。
C++中, 虚函数,抽象基类,动态绑定和多态构成了出色的动态特性。
RTTI事指运行时类型识别(Run-time type identification)在只有一个指向基类的指针或引用时确定一个对象的准确类型。
单个参数的构造函数,其参数是与它同属一类的对象的(常)引用;类定义中,如果未提供自己的拷贝构造函数,C++提供一个默认拷贝构造函数,该默认拷贝构造函数完成一个成员到一个成员的拷贝
浅拷贝是创建了一个对象用一个现成的对象初始化它的时候只是复制了成员(简单赋值)而没有拷贝分配给成员的资源, 仍然共用同一个内存空间(如给其指针变量成员分配了动态内存);
深拷贝是当一个对象创建时,如果分配了资源,就需要定义自己的拷贝构造函数,使之不但拷贝成员也拷贝分配给它的资源,在堆区重新开辟内存空间
开发时间短,效率高,可靠性高。面向对象编程的编码具有高可重用性,可以在应用程序中大量采用成熟的类库(如STL),从而虽短了开发时间,软件易于维护和升级。
reserve()用于让容器预留空间,避免再次分配内存;
capacity()返回在重新进行分配以前所能容纳的元素数量。
内存泄漏一般是指堆内存的泄漏,也就是程序在运行过程中动态申请的内存空间不再使用后没有及时释放,导致那块内存不能被再次使用。
解决方法:
1)及时释放掉内存或系统资源;
2)重载new和delete,以链表的形式自动管理分配的内存;
3)使用智能指针,share_ptr、auto_ptr、weak_ptr。
该指针用于确保程序 不存在内存和资源泄漏 异常安全。
(1)auto_ptr:一个指针变量指向的空间不能由两个auto_ptr管理,不然会析构两次,使程序崩溃。
(2)scoped_ptr: 防拷贝,粗暴的方式(推荐使用)
scoped_array: 防拷贝,只是scoped_array管理的对象是数组,需要重载[]的形式。
(3)shared_ptr: 加入了引用计数,从而很好的规避了auto_ptr 释放两次空间,调两次析构的情况
(4)shared_array:管理的对象是数组,需要重载[]的形式。
(5)week_ptr——为了解决循环引用的问题(和shared_ptr配合使用)
1.编译器处理方式 :1)define – 在预处理阶段进行替换 ; 2)const – 在编译时确定其值
2.类型检查 :1)define – 无类型 ;2)const – 有数据类型
3.内存空间 :1)define – 不分配内存,给出的是立即数,有多少次使用就进行多少次替换,在内存中会有多个拷贝,消耗内存大 ;2)const – 在静态存储区中分配空间,在程序运行过程中内存中只有一个拷贝
1)sizeof是操作符,参数为任意类型,主要计算类型占用内存大小。
2)strlen()是函数,strlen只能计算以"\0"结尾字符串的长度,计算结果不包括"\0"。
构造函数:“先基后派”;
析构函数:“先派后基”。
1.成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。
2.如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。
3.类中const成员常量必须在构造函数初始化列表中初始化。
4.类中static成员变量,只能在类内外初始化(同一类的所有实例共享静态成员变量)。
初始化顺序:
1) 基类的静态变量或全局变量
2) 派生类的静态变量或全局变量
3) 基类的成员变量
4) 派生类的成员变量
cast发生的时间不同,一个是static编译时,一个是runtime运行时;
static_cast是相当于C的强制类型转换,用起来可能有一点危险,不提供运行时的检查来确保转换的安全性。
dynamic_cast用于转换指针和和引用,不能用来转换对象 ——主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。在多态类型之间的转换主要使用dynamic_cast,因为类型提供了运行时信息。
大端与小端是用来描述 多字节数据在内存中的 存放顺序,即字节序。
大端(Big Endian)指 低地址端存放高位字节
小端(Little Endian)指低地址端存放低位字节
各自优势:1)Big Endian:符号位的判定固定为第一个字节,容易判断正负。
2)Little Endian:长度为1,2,4字节的数,排列方式都是一样的,数据类型转换非常方便。
在该函数前添加extern “C”声明。
由于编译后的名字不同,C++程序不能直接调用C 函数。
c++中class和struct的区别(至少两点)
(1) c++中的class默认的成员是私有(private)的,struct默认的是共有的。
(2) c++中的class可以定义成员函数,struct只能定义成员变量。
声明变量不分配空间,定义变量要分配空间。
定义其实包含了声明的意思,同时要分配内存空间。
memset ,memcpy 的区别
Memset(memoryset):对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为’\0′;
memcpy(memorycopy):内存拷贝,拷贝任何数据类型的对象 或者 指定拷贝的数据长度;memcpy的用途更广。
1 耗时操作使用线程,提高应用程序响应
2 并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求。
3 多CPU系统中,使用线程提高CPU利用率
其他情况都使用单线程。
auto自动变量:表明变量自动具有本地范围,在离开作用域,无论块作用域,文件作用域还是函数作用域,变量都会被程序隐藏或自动释放。然后等你重新进入该作用域,变量又重新被定义和调用。使用auto变量优势是无需考虑变量是否被释放(离开作用域自动释放/隐藏,进入作用域重新被定义或者调用)。
static静态变量:在函数等调用结束后,该变量也不会被释放,保存的值还保留。即它的生存期是永久的,直到程序运行结束,系统才会自动释放。
extern外部变量:属于变量声明,extern int a和int a的区别就是,前者告诉编译器,有一个int类型的变量a定义在其他地方,如果有调用请去其他文件中查找定义。
构造函数不能是虚函数,但是析构函数通常是虚函数
1)假设构造函数是虚函数,需要通过vtable(简称虚基表)来调用,但是vtable却是在构造函数中进行初始化的,产生了矛盾,所以两者的顺序不能倒置。
2)对于析构函数,虚基表已经在构造函数中初始化好了,我们通常通过基类的指针来销毁对象。若析构函数不为虚函数,就不能正确识别对象类型,从而不能正确销毁对象。
为了实现链式操作,将目的地址返回;返回的是dest吧,怎么便于下次拷贝
DLLHELL(DLL地狱) 听说过吗
DLL动态链接库是程序复用的重要方式,DLL可以导出函数,使函数被多个程序复用,DLL中的函数实现 可以被修改而无需重新编译和连接 使用该DLL的应用程序。
导出类的DLL在维护和修改时有很多地方必需很小心,增加成员变量、修改导出类的基类等操作都可能导致意想不到的后果,也许用户更新了最新版本的DLL库后,应用程序就再也不能工作了。这就是著名的DLLHell(DLL地狱)问题。
DLL地狱是因为DLL当初是作为函数级共享库设计的,并不能真正提供一个类所必需的信息。类层上的程序复用只有Java和C#生成的类文件才能做到。
伙伴内存管理?
只知道伙伴系统是Buddy system
一个结构体变量定义完之后,其在内存中的存储并不等于其所包含元素的宽度之和。
规则一:结构体中元素按照定义顺序依次置于内存中,但并不是紧密排列。从结构体首地址开始依次将元素放入内存时,元素会被放置在其自身对齐大小的整数倍地址上。这里说的地址是元素在结构体中的偏移量,结构体首地址偏移量为0。
规则二:如果结构体大小不是所有元素中最大对齐大小的整数倍,则结构体对齐到最大元素对齐大小的整数倍,填充空间放置到结构体末尾。
规则三:基本数据类型的对齐大小为其自身的大小,结构体数据类型的对齐大小为其元素中最大对齐大小元素的对齐大小。
智能指针:像指针一样使用的值,但提供了自动内存管理的附加功能:当指针不再使用时,它指向的内存被释放
共享指针:管理动态创建的对象的销毁。它的基本原理就是记录对象被引用的次数,当引用次数为 0 的时候,共享指针的析构函数就把指向的内存区域释放掉。
分段机制把一个逻辑地址转换为线性地址;
分页机制把一个线性地址转换为物理地址。
逻辑地址(启动分段) -> 线性地址(启动分页) -> 物理地址
虚拟地址:虚拟内存映射出来的地址
逻辑地址:程序的段 加 偏移量形成的,取址求出来的地址
线性地址:逻辑地址到物理地址的中间层,只有启动分页机制的时候才有线性地址,如果没有分页机制,那么线性地址就是物理地址
物理地址:内存中实实在在存在的硬件地址
x++ > x+=1 > x=x+1
x++:读取x的值->x自增1
x+=1:读取右x的地址->x值+1->将得到的值传回给x(x地址已经读出)
x=x+1:读取右x的地址->x值+1->读取左x的地址(并不知道左右是同一个x,所以要读取两遍)->将右值传给左值
能,局部会屏蔽全局。要用全局变量,需要使用"::"
static全局变量/局部变量/函数与普通的全局变量/局部变量/函数有什么区别?
全局变量:
1)static全局变量:静态全局变量限制了其作用域,只在定义该变量的源文件。在同一源程序的其它源文件中不能使用它。static全局变量只初始化一次,防止在其他文件单元中被引用;
2)普通全局变量:非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。
局部变量:
1)static局部变量:由动态存储方式改变为静态存储方式。
只被初始化一次,下一次依据上一次结果值;
2)普通局部变量:动态存储方式,存储在堆栈中。
从分配内存空间看:全局变量,静态局部变量,静态全局变量都在静态存储区分配空间
而局部变量在栈里分配空间。
1)static函数:作用域仅在本文件中,加上static就是内部函数—在当前源文件中说明和定义。static函数在内存中只有一份;
2)普通函数:在当前源文件以外使用,需应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。普通函数在每个被调用中维持一份拷贝。
如何限制一个类对象只能在堆(栈)上分配空间
只能在堆上分配类对象——
(1)不能静态建立类对象,即不能直接调用类的构造函数;
(2)将析构函数设为私有,类对象就无法建立在栈上了
size()指容器当前拥有的元素个数;
capacity()指容器在必须分配存储空间之前可以存储的元素总数。
三大特征:封装、继承、多态
四大特征:抽象、封装、继承、多态
定义:封装就是将抽象得到的数据和行为(属性和行为)相结合,形成一个有机的整体(类),其中数据和函数都是类的成员,目的在于将对象的使用者和设计者分开,以提高软件的可维护性和可修改性
特性:
定义:新类(子类)从已有类(父类)那里得到已有的特性。
原有的类成为基类或父类,产生的新类称为派生类或子类
被继承的是父类(基类),继承出来的类是子类(派生类),子类拥有父类的所有的特性。
继承方式有公有(public)、私有(private),保护(protect)。默认是私有继承
公有继承中父类的公有和保护成员在子类中不变,私有的在子类中不可访问。
私有继承中父类的公有和保护成员在子类中变为私有,但私有的在子类中不可访问。
保护继承中父类的公有和保护成员在子类中变为保护,但私有的在子类中不可访问。
优点:减少了重复的代码、继承是多态的前提、继承增加了类的耦合性, 继承可以扩展已存在的代码;
缺点:继承在编译时刻就定义了,无法在运行时刻改变父类继承的实现;父类通常至少定义了子类的部分行为,父类的改变都可能影响子类的行为;如果继承下来的子类不适合解决新问题,父类必须重写或替换,那么这种依赖关系就限制了灵活性,最终限制了复用性。
虚继承:为了解决多重继承中的二义性问题,它维护了一张虚基类表。
定义:“一个接口,多种方法”,程序在运行时才决定要调用的函数,意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
实现多态的条件:
1.派生类对象必须赋值给基类的引用或者指向基类的指针。
2.派生类的继承方式必须是公有继承public。
如果是protected继承或者是private继承,那么子类的所有方法只能在内部访问,这时候就不需要多态了。一般会有语法报错。
3.基类中的同名函数必须定义为虚函数。
静态多态是通过函数重载实现的;动态多态是通过虚函数实现的。
注:多态与非多态的实质区别就是函数地址是静态绑定还是动态绑定。
静态绑定:如果函数的调用在编译器编译期间就可以确定函数的调用地址,并产生代码动态绑定:如果函数调用的地址是 需要在运行期间才确定
目的:接口重用
用法:声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。
用一句话概括:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
虚函数:允许被其子类重新定义的成员函数,引入虚函数的目的是为了动态绑定;
虚函数的声明:virtual returntype func(parameter);
纯虚函数:virtual returntype func(parameter)=0;(纯虚函数使其值=0)
引入纯虚函数是为了派生接口。(使派生类仅仅只是继承函数的接口)
基类为什么需要虚析构函数?
防止内存泄漏。
前提条件:想去借助父类指针去销毁子类对象的时候,不能去销毁子类对象。假如没有虚析构函数,释放一个由基类指针指向的派生类对象时,不会触发动态绑定,则只会调用基类的析构函数,不会调用派生类的。
后果:派生类中申请的空间则得不到释放导致内存泄漏。
Overload(重载):将语义、功能相似的几个函数用同一个名字表示,但参数或返回值不同(包括类型、顺序不同),即函数重载。
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数列表(参数类型/参数个数/顺序)不同;
(4)virtual 关键字可有可无。
Override(覆盖或重写):是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数列表相同;
(4)基类函数必须有virtual 关键字。
注:重写基类虚函数的时候,会自动转换这个函数为virtual函数,不管有没有加virtual,因此重写的时候不加virtual也是可以的,不过为了易读性,还是加上比较好。
Overwrite(重写):隐藏,是指派生类的函数屏蔽了与其同名的基类函数,规则如下:(
1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏;
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏
在C++,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区;
在C中,内存区分4个区,分别为堆、栈、全局/静态存储区、常量存储区;
栈:由编译器自动分配释放,编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。
堆:由new分配的内存块,其释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
自由存储区:由malloc等分配的内存块,用free来结束自己的生命的。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中,
常量存储区:存放的是常量,不允许修改
(1)栈是由编译器自动分配释放;堆的释放工作由程序员控制,容易产生内存泄漏
(2)堆内存可以达到4G的空间;对于栈来讲,默认的栈空间大小是1M
(3)堆频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低;栈是先进后出的队列,以至于永远都不可能有一个内存块从栈中间弹出
(4)堆的生长方向是向上的,向着内存地址增加的方向;
栈的生长方向是向下的,向着内存地址减小的方向。
(5)堆都是动态分配的,没有静态分配;栈有静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配;动态分配是由编译器进行释放,无需我们手工实现。
(6)栈是机器系统提供的数据结构,栈的效率比较高;堆则是 C/C++ 函数库提供的,它的机制是很复杂的,堆的效率比栈要低得多。
标准模板库(STL),STL有7种主要容器:vector, list, deque, map, multimap, set, multiset.
vector、map、multimap、unordered_map、unordered_multimap的底层数据结构,以及几种map容器如何选择?
底层数据结构:
vector基于数组,map、multimap基于红黑树,unordered_map、unordered_multimap基于哈希表。
根据应用场景进行选择:
map/unordered_map 不允许重复元素
multimap/unordered_multimap 允许重复元素
map/multimap 底层基于红黑树,元素自动有序,且插入、删除效率高
unordered_map/unordered_multimap 底层基于哈希表,故元素无序,查找效率高。