- 1.const关键字
- 2.static关键字
- 3.this指针
- 4.Inline内联函数
- 虚函数可以是内联函数吗?
- 5.volatile关键字
- mutable
- explicit
- 6.asset
- 7.extern
- 8.C++中struct与class区别
- 9.面向对象
- 1.封装
- 2.继承
- 3.多态
- 4.虚函数、纯虚函数
- 5.虚析构函数
- 6.虚表指针、虚函数表
- 6.虚指针、虚函数表
- 7.虚继承
- 8.构造函数可以是虚函数吗
- 10.C++ 内存空间布局
- 11.new/delete, malloc/free
- 12.内存对齐
- 1.类的对象存储空间
- 2.为什么要内存对齐
- 13.指针和引用的区别
- 野指针
- 空悬指针
- 14.定义一个只能在堆(栈)上生成对象的类
- 15.智能指针
- 16.类型转换运算符
- 17.C++11特性
- 1.nullptr
- 2.auto/decltype
- 3.Lambda表达式
- 4.右值引用和move语义
- 18.STL容器简介
- 1.array
- 2.vector
- 3.list
- 4.deque
- 5.set
- 6.map与unordered_map
- 修饰变量,表示改变量不可以更改
- 修饰指针,有指向常量的指针和指针常量
- 常量引用,常作为形参类型,既避免了拷贝,又避免了值被修改
- 修饰成员函数,说明该成员函数内不能修改成员变量。
常量对象成员,只能在初始化列表中赋值
2.static关键字- 修饰普通变量,修改了变量的存储区域和生命周期,使变量存储在静态区,在*main函数开始之前就会分配空间,*没有初始值则就会用默认值初始化
- 修饰普通函数,作用域隐藏,此函数只能在定义该函数的文件内使用
- 修饰成员变量,被整个类所拥有,不需要生成对象就可以访问
- 修饰成员函数,也是被整个类所有拥有,不需要生成对象就可以访问该函数,但是static函数不能访问非静态成员。
- this指针是一个隐含于每一个非静态成员函数中的特殊指针,它指向调用该成员函数的那个对象
- this指针并不是一个常规变量,而是一个右值(不能取地址),故不能取得this指针的地址
- 当一个对象调用一个成员函数的时候,编译程序先将对象的地址赋值给this指针,每次调用成员函数存取数据成员时,都隐式使用this指针
- 内联函数在编译时直接将函数代码嵌入到目标代码中,省去函数调用的开销来提高执行效率,并且进行参数类型检查,具有返回值,可以实现重载
- 相当于宏,却比宏多了类型检查,宏只是简单的字符串替换;内联函数介意访问类的成员变量,而宏不可以
- 内联函数要求是逻辑简单且重复被使用的函数,不应该包含循环,递归等 *** 作
- 在类声明中定义的函数,除了虚函数都会隐式的被当成内联函数。
优缺点:
- 内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。
如果执行函数体内代码的时间比函数调用要久,那意义就不大
- 是否内联,程序员不可控。
内来函数只是对编译器的建议,决定权在编译器
- 可以,内联函数可以修饰虚函数,但当虚函数表现多态性的时候不能内联,因为内联函数是在编译期建议编译器内联,而虚函数多态是在运行期。
- Inline virtual可以内联的时候:编译器知道所调用的对象是哪个类,只有在编译器就具有实际对象,而不是对象的指针或者引用时。
- volatile关键字是一种类型修饰符,volatile声明的类型变量表示可以被某些编译器的未知因素更改(如 *** 作系统,硬件,线程等)。
使用volatile会告诉编译器不应对这样的对象进行优化
- volatile关键字修饰的变量,每次访问时都必须从内存中取值(普通变量可能会从CPU寄存器中取值)
explicitmutable中文意思是“易变的,可变的”,也是为了突破const的限制而设置的。
被mutable修饰的变量,永远是可变的。
有些时候,mutable变量在常函数中也可以修改;当需要在const函数里面修改一些跟类状态无关的数据成员,那么这个函数就应该被mutable来修饰,并且放在函数后后面关键字位置。
6.assetexplicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生隐式类型转换,只能以显式的方式发生类型转换,注意以下几点:
- explicit只能用来于类内部的构造函数的声明上
- explicit 关键字作用于单个参数的构造函数
- 不能发生相应的隐式类型转换
- 断言,是宏,而不是函数
- 断言的作用是其条件返回错误,则终止程序运行
- 可以使用NODEG关闭assert,但需要在源代码最开头,#include之前
- extern用在变量或者函数的声明前,表明它们在别处定义,在此处使用。
- 在程序中加上extern "C"后,相当于告诉编译器这部分代码是C语言写的,因此要按照C语言进行编译
struct更适合当做一个数据结构的实现体,class更适合当做一个对象的实现体
- 默认数据访问权限不同,struct的权限是public,而class的是private
- 默认的继承访问权限也不同,struct的权限是public,而class的是private
面向对象程序设计(Object-oriented programming,OOP)是种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。
面向对象三大特性:封装,继承,多态
1.封装2.继承将客观事物封装成抽象的类,隐藏实现细节。
将自己的数据和方法提供给可信的类或者对象访问,不可信的则信息隐藏。
- public:可以被任意实体访问
- protected:只允许子类及本类的成员函数、友元函数访问
- private:只允许本类中成员函数、友元函数访问
继承:某种类型对象获得了另一种对象类型的属性和方法
3.多态多态,即多种形态,是同一个行为,多种实现,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为;多态是以封装和继承为基础。
4.虚函数、纯虚函数多态有静态多态和动态多态两种
- 静态多态(编译期/早绑定)
- 实现方法:函数重载
- 重载是允许有多个同名函数,但参数类型,参数个数,返回类型可能不同
- 动态多态(运行期/晚绑定)
- 实现方法:虚函数覆盖
- 重写:派生类重新定义基类的虚函数
- 多态必须是通过基本的指针或引用调用虚函数
5.虚析构函数虚函数:virtual int A();
再基类中冠以关键字virtual修饰的成员函数为虚函数,它提供了一个接口界面,允许派生类对基类中的虚函数重新定义
纯虚函数:virtual int A() = 0;
- 是一种特殊的虚函数,它通常作为接口存在,纯虚函数不具备函数的功能,一般不能直接调用
- 拥有纯虚函数的类叫做抽象类,抽象类必须用作派生其他类的基类,不能用于直接创建对象实例。
但仍可使用指向抽象类的指针支持运行时多态。
6.虚表指针、虚函数表为了防止内存泄漏。
因为将基类的指针或引用绑定到派生类的对象,如果未将基类析构函数定义为虚函数,当我们调用析构函数时,那么只会调用基类析构函数,释放基类的内存空间,派生类的内存空间不会释放,则会造成内存泄漏。
6.虚指针、虚函数表虚表指针:在含有虚函数的类的实例化对象中,指向虚函数表,在运行时确定
虚函数表:含有虚函数的类中编译器都会创建一个虚函数表,存放着类中所有虚函数的地址
存在虚函数时,每个对象都有一个指向虚函数表的指针
虚函数表是一个一维数组,存放着类中所有虚函数的地址
虚函数表是与类对应的,虚表指针则是对象对应
虚表可以继承,派生类如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。
如果派生类有自己的虚函数,那么虚表中就会添加该项。
7.虚继承虚函数指针:在含有虚函数的类的实例化对象中,指向虚函数表
虚函数表:含有虚函数的类中编译器会自动创建一个虚表,存放类中所有虚函数的地址
- 含有虚函数的每个对象都有一个指向虚函数表的指针,虚指针指向虚函数表
- 虚函数表是对于类,虚指针是对于对象
8.构造函数可以是虚函数吗虚继承用于解决多继承中存在的菱形继承问题(浪费资源,存在二义性)
虚继承与虚函数:
- 都利用了虚指针与虚表
- 虚函数中的虚表存放的是虚函数的地址
- 虚继承中的虚基类表中存储的是相对直接继承类的偏移
- 不可以。
构造函数是实例化对象时自动调用的,如果将构造函数定义为虚函数,而此时对象并未创建,则不能调用虚函数,故不能定义成虚函数。
(虽然虚函数表在编译期就已经存在了,但是虚函数指针存在于对象实例化的内存空间中,故先有了对象才能通过虚函数指针获取虚函数表中的地址进行调用)
- 从类型上看,构造函数实例化对象时需要明确其类型;而虚函数主要是在信息不全的情况下,能使覆盖的函数得到调用。
- 从使用角度来看,构造函数是实例化对象时自动调用;虚函数是基类的指针指向派生类的对象时,通过该指针实现对派生类的虚函数的调用。
- 栈区:在执行函数时,函数内的局部变量的存储单元都可在栈上创建,由编译器分配和释放,效率高,但内存容量有限制
- 堆区:动态申请的内存空间,由使用者分配和释放。
如果使用者没有释放掉,则程序结束后会由 *** 作系统释放掉。
- 自由存储区:如果说堆是 *** 作系统维护的一块内存,则自由存储区是C++中通过new和delete动态分配和释放对象的一个抽象概念。
自由存储区与堆比较像,但并不等价
- 全局/静态区:用于存储全局变量和静态变量的一块内存
- 常量区:存放的常量,不允许被修改
- 代码区:存放程序体的二进制代码,比如我们写的函数,都是在代码区的。
相同点:都是用于内存的动态申请和释放
不同点:
- new/delete是运算符,malloc/free是标准库函数,需要库文件支持
- new/delete分别会调用构造函数与析构函数,malloc/free没有相关调用
- new是类型安全的,malloc不是
- new会自动计算分配的内存大小,而malloc需要手动计算
2.为什么要内存对齐1.非静态成员的数据大小之和
2.编译器额外加入的成员变量(如指向虚函数表的指针)
3.为了边缘对齐优化加入额外大小
空类的对象大小为1
- 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同
- 未说明原因,则按最大size的成员对齐
内存对齐的原因:内存对齐可以提高存储效率,可以提高性能
13.指针和引用的区别- 引用是一个别名,使用必须初始化;指针是一个变量,存储的是一个地址,可以为空
- 引用一经绑定就不可更改,指针是可以改变指向对象的
- 引用是一个别名,并不占用内存空间,指针具有内存空间
- 指针可以是多级,引用只能是一级
指向的一块不可用内存区域的指针,不是NULL指针,是指向“垃圾”内存的指针
- 指针定义是未被初始化
- 释放指针后未置空
- 指针 *** 作超过了变量的作用范围
指向最初指向的内存已经被释放了的指针,即为指向一块未分配给用户的内存的指针
14.定义一个只能在堆(栈)上生成对象的类new一般分为三种:new operator(new表达式/new关键字)、operator new(new *** 作符)、placement new
new一般申请内存空间,有几个个阶段:
- 申请内存空间,大小为指定类型对象所占用的内存大小。
申请内存空间实际上是调用operator new
- 在已经分配好的空间上,调用构造函数生成类对象。
使用placement new实现
- 返回相应指针
只在栈上:将new重载为私有。
因为在堆上创建对象,需要使用new(new 调用operator new分配内存),禁用new则无法在堆上创建对象。
只在堆上:将析构函数设置成私有函数。
因为编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会去查看析构函数的访问性。
若析构函数不可访问,则不能在栈上创建对象。
- auto_ptr(在C++11中已被弃用)
- 主要是为了解决有异常抛出时出现的内存泄漏
- auto_ptr没有引用计数,故一个对象只能由一个auto_ptr拥有,在给其他auto_ptr赋值的时候,会转移所有权
- auto_ptr赋值、拷贝之后,源对象的变得无效,故这是弃用原因
- Shared_ptr
- 采用引用计数器的方法,允许多个指针指向同一对象,当计数器为0的时候自动释放动态分配的资源
- Unique_ptr
- 独占指针采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源;故不支持赋值和拷贝 *** 作
- Weak_ptr
- 引用计数窜在一个问题,就是相互引用时会造成环形引用,这样两个指针的内存都不能释放。
需要weak_ptr打破环形引用
- weak_ptr允许共享但不拥有某对象,也就是只引用,不计数。
一旦最末一个拥有该对象的智能指针失去了所有权,任何 weak_ptr 都会自动成空(empty)。
- 引用计数窜在一个问题,就是相互引用时会造成环形引用,这样两个指针的内存都不能释放。
- static_cast static_cast (expression)
- 用于转换基础类型和具有继承关系的对象指针
- 无运行时类型检查
- dynamic_cast
- 用于多态类型的转换
- 执行运行时有类型检查
- 只适用于指针和引用
- reinterpret_cast
- 强制转换,可以将任意类型指针转换成其他任意类型指针
- reinterpret_cast 运算符不能丢掉 const、volatile 或 __unaligned 特性。
- const_cast:用来修改类型的const或volatile属性
2.auto/decltype在C语言中,将NULL定义为(void*)0;在C++中将NULL定义为0,如果参数为整型或者指针的时候,使用NULL就会不明确。
在C++中,指针必须有明确的类型定义。
nullptr能明确区分整型和指针,并根据实际情况自动转换成相应的指针类型,但不会转换成整型,所以不会造成参数传递错误
C++不允许()(void*)0)隐式转换至其他类型,C++11引入了关键字nullptr,专门用来区分0,空指针
-
auto
- auto让编译器通过初始值了来进行类型推导,故auto必须初始化
- auto不能用于函数传参和推导数组类型
-
decltype(表达式)
-
decltype关键字是为了解决auto关键字只能对变量进行推导的缺陷而出现的
-
在此过程中**,编译器分析表达式并获得它的值,并不实际计算表达式的值**
-
不论是顶层const还是底层const, decltype都会保留,引用也会保留
-
int func() { return 0;} decltype(func()) test = 10; int a = 0; decltype(a) b =4;
-
Lambda表达式,提供了一个类似匿名函数的特性,匿名函数就是需要一个函数,但是又不想费力去创建一个命名函数的时候去使用的
//[capture] (params) opt -> ret { body; };
capture是捕获列表, params是参数列表, opt是函数选项,ret是返回值类型,body是函数体
- 捕获列表:捕获一定范围的变量
- --不捕捉任何变量
- [&] --捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)
- [=] – 捕获外部作用域中所有变量,并作为副本在函数体内使用 (按值捕获)
- [bar] --按值捕获bar,同时不捕获其他变量
- [this] --捕获当前的this指针,可以在lambda中使用当前类的成员函数和成员变量
- 参数列表,选题
- opt,选填
- mutable:说明表达式代码可以修改被值捕获的变量
- exception:说明是否抛出异常以及何种异常
- 返回值类型,选填,通过返回值后置语法定义
- 函数体,不能省略,但可为空
- 对于没有捕获任何变量的 lambda 表达式,还可以转换成一个普通的函数指针
- lambda 表达式一个更重要的应用是其可以用于函数的参数,通过这种方式可以实现回调函数。
int a = 0;
auto f = [] {return a;};
class Test
{
public:
int m_i = 0;
void output(int x, int y)
{
auto x1 = [] {return m_i;}; //error
auto x2 = [=] {return m_i + x + y;};//ok
auto x3 = [&] {return m_i + x + y;};//ok
auto x4 = [this] {return m_i;};//error
auto x5 = [this] {return m_i + x + y;};//ok
}
};
4.右值引用和move语义
-
左值和右值
- 左值:存储在内存中,有明确存储地址(可取地址)的数据
- 右值:是可以提供数据值得数据(不可取地址)
- 简单的区分方法:**可以取地址的就是左值,不可以取地址的就是右值。
**所有有名字的变量或者对象都是左值,而右值时匿名的
-
右值引用:语法是T&&
- 对于C++11,编译器会根据参数是左值还是右值在复制构造函数和转移构造函数间进行选择
- 拷贝构造函数执行的是深拷贝,因为源对象本身不能被改变。
而转移构造函数却可以复制指针,将源对象的指针置空,这种形势下,是安全的,因为因为不可能再使用这个对象了
- 右值引用具有移动语义,移动语义可以将资源(堆、系统对象等)通过浅拷贝从一个对象转移到另一个对象这样就能减少不必要的临时对象的创建、拷贝以及销毁,可以大幅提高 C++ 应用程序的性能。
-
转移左值
- 有时候,需要编译器将左值当成右值对待,故提供了std::move
- move:仅仅是简单地将左值转换为右值,本身并没有转换任何东西。
仅仅是让对象可以转移。
-
深拷贝浅拷贝
- 浅拷贝:对一个已知对象拷贝,编译系统会自动调用一个构造函数–拷贝构造函数,若用户没有自定义构造函数,则会调用默认构造函数,调用一次构造函数,一次默认拷贝构造函数,调用两次析构函数,两个对象的指针成员指向同一块内存,但是结束时内存被释放了两次,会造成内存泄漏的问题。
- 深拷贝
- 对含有指针成员的对象进行拷贝时,必须自定义拷贝构造函数,是拷贝后的对象有自己的内存空间,即为深拷贝
- 深拷贝避免了内存泄漏问题,调用一次构造函数,一次自定义拷贝构造函数,两次析构函数,两个对象所指的内存不同
总结:浅拷贝知识对指针的拷贝,拷贝后的两个对象指向通一块内存;而深拷贝不仅仅是对指针的拷贝,而且对指针指向的内容也进行了拷贝,经深拷贝后的两个指针指向不同的内存地址。
- 浅拷贝:对一个已知对象拷贝,编译系统会自动调用一个构造函数–拷贝构造函数,若用户没有自定义构造函数,则会调用默认构造函数,调用一次构造函数,一次默认拷贝构造函数,调用两次析构函数,两个对象的指针成员指向同一块内存,但是结束时内存被释放了两次,会造成内存泄漏的问题。
…
18.STL容器简介容器,置物之所也
研究数据的特定排列方式,以利搜寻或排序或其他特殊目的,这一门学科我们称之为数据结构。
序列式容器:每个元素只有一个value值,其中元素可以有序,可无序
关联式容器:每个元素有一个key值和value,插入某元素时,容器内部会根据key值按照某种特定规则将这个元素放入合适位置
1.array- 是一个固定大小的序列式容器,保存了一个以严格的线性顺序排列的特定数量
- 支持快速随机访问
- 可以改变大小的序列式容器,维护的是一块线性连续空间,支持快速随机存取
- vector动态增加大小时,当原来的空间不满足时,则会申请原大小两倍的新空间,将原来的元素拷贝到新空间,再将原来的的空间释放掉。
简而言之,就是配置新空间,数据移动,释放旧空间。
- 双向链表,是序列式容器。
允许在任何地方进行常数级的删除和插入 *** 作
- 双端队列,是具有动态大小的序列容器
- vector是单向开口的连续线性空间,deque是双向开口的连续线性空间,由一段一段的定量连续线性空间构成
- deque 允许以常数时间内对头端进行元素的插入或移除 *** 作
set的特性是,所有元素都会根据元素的键值自动被排序。
set只有key值,key值就是value值,不允许有相同的键值。
以红黑树为底层机制。
- map
- 是关联式容器,所有的元素都会根据元素的key值被排序。
map的元素是pair,即拥有key值和value值,
- 由于红黑树是一种平衡二叉树,自动排序效果很不错,故map以红黑树为底层结构,保证了一个较为稳定的动态 *** 作时间,查询、插入,删除都是O(log n)
- 是关联式容器,所有的元素都会根据元素的key值被排序。
- unordered_map
- 是关联式容器(无序容器),元素是pair,即拥有key值和value值。
- 底层结构是哈希表,查询时间是O(1)
- 是关联式容器(无序容器),元素是pair,即拥有key值和value值。
未完待续…
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)