封装,继承,多态
封装:把对象的属性私有化,同时提供可以被外界访问这些属性的方法。(如果属性不想被外界访问,那大可不必提供方法给外界访问;但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了)
继承
继承:是使用已存在的类的定义,作为建立新类的基础技术,新类可以增加新的属性或新的方法,也可以用父类的功能,但不能选择性地继承。通过使用继承,能够非常方便地复用这些代码。
多态:表现为程序中定义的引用变量,所指向的具体类型和具体调用的方法,在编译期并不能确定,而是在程序运行期确定。
一个程序被加载到内存中,这块内存首先就存在两种属性:静态分配内存和动态分配内存。
.text: 也称为代码段(Code),用来存放程序执行代码,同时也可能会包含一些常量(如一些字符串常量等)。该段内存为静态分配,只读(某些架构可能允许修改)。
这块内存是共享的,当有多个相同进程(Process)存在时,共用同一个text段。
.data: 也有的地方叫GVAR(global value),用来存放程序中已经初始化的非零全局变量。静态分配。
data又可分为读写(RW)区域和只读(RO)区域。
-> RO段保存常量所以也被称为.const data
-> RW段则是普通非常全局变量,静态变量就在其中
.bss: 存放程序中为初始化的和零值全局变量。静态分配,在程序开始时通常会被清零。
text和data段都在可执行文件中,由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化。
这三段内存就组成了我们编写的程序的本体,但是一个程序运行起来,还需要更多的数据和数据间的交互,否则这个程序就是死的,无用的。所以我们还需要为更多的数据和数据交互提供一块内存——堆栈
堆和栈都是动态分配内存,两者空间大小都是可变的。
Stack: 栈,存放Automatic Variables,按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但自由性差,最大空间不大。
Heap: 堆,自由申请的空间,按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但自由性大,可用空间大。
每个线程都会有自己的栈,但是堆空间是共用的。
结构体内存对齐:元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始。
内存对齐可以大大提升内存访问速度,是一种用空间换时间的方法。
内存不对齐会导致每次读取数据都会读取两次,使得内存读取速度减慢。
cpu把内存当成是一块一块的,块的大小可以是2,4,8,16 个字节,因此CPU在读取内存的时候是一块一块进行读取的。
多态:同一个对象在不同场景下的多个形态
多态分为静态多态和动态多态
静态多态的实现依靠函数重载,编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数可以调用就调,没有的话就会发出警告或者报错。
动态多态的实现依靠虚函数来实现,意思是在程序运行时根据父类指针指向对象来决定应该调用哪个子类的虚函数
它的好处在于提高了代码的扩展性
但是它的缺点是降低了程序运行的效率,因为动态多态需要去找虚表的地址,还有一点造成了空间的浪费,因为要建立虚表。
重载是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。实现原理上:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。
重写是指子类函数覆盖父类函数(是指子类重新定义父类虚函数的方法)。 实现原理:当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)
重定义:重定义也叫隐藏,指的是在继承关系中,子类实现了一个和父类名字一样的函数,(只关注函数名,和参数与返回值无关)这样的话子类的函数就把父类的同名函数隐藏了。
静态编译就是在编译时,把所有模块都编译进可执行文件里,当启动这个可执行文件时,所有模块都被加载进来;
动态编译是将应用程序需要的模块都编译成动态链接库,启动程序(初始化)时,这些模块不会被加载,运行时用到哪个模块就调用哪个
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序
静态链接把所有程序模块都链接成一个单独的可执行文件
静态链接优缺点:空间大,时间短
一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
动态链接优缺点:空间小,时间长
动态态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失
本质上来说库是可执行代码的二进制形式,按照使用方式的不同分为静态库和动态库
静态库和动态库最本质的区别就是:该库是否被编译进目标(程序)内部。
分别介绍:
静态(函数)库
一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。
这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。
动态函数库
动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。
与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。
WINDOWS下:.dll为动态库 .lib为静态库
LINUX下:.so为动态库 .a为静态库
new 用于单个对象或实例创建,就是调用类的构造函数。
new [] 用于创建对象或实例的数组实例,并且地址是连续的。(内存分配的时候有可能不连续,但地址链表是连续的。
一个为调试版本,其中包括了出错时能够定位源代码的在行,如果源文件已经改变,定位出来会有偏移,而且,在这个版本中编译器不会进行代码优化,并且在程序中能用宏定义_DEBUG来确定当前的版本。另一个为正试版本,程序出错只是进行简单的错误处理,编译器会优化代码,以提高性能。Release代码更小,执行更快,编译更严格,更慢,当然就没有了调试信息。
a. new,delete是操作符,可以重载,只能在C++中使用
b. new和delete能执行构造和析构,malloc和free不行
c. new和delete返回的是对象指针,而malloc和free返回void *指针
a. malloc和free是C/C++的标准库函数,而new和delete是C++操作符
b. 对于C/C++内部数据类型的对象,malloc和free无法满足动态对象的要求,因为对象的创建和销毁要用到构造函数和析构函数,由于malloc和new非运算符,不在编译器控制权限之内,不能把执行构造析构的任务强加在malloc和free上,所以C++需要一个能动态分配内存和初始化的new,以及一个可以释放和清理的delete(关于是否删除指针可引向智能指针)
New做了两件事,一个是分配内存,一个是调用构造函数,失败会抛出bad_alloc异常,可以使用try-catch进行捕获,分配内存这块的底层是malloc,调用构造函数
malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。 但如果合并之后还是不够申请大小呢,怎么办?此时分配器会调用sbrk函数,向内核请求额外的堆存储器,分配器将额外的存储器转换为一个大的空闲块,然后将这个块插入到空闲链表中,然后将被请求的块放置在这个新的空闲块中。
地址空间限制是有的,但是malloc通常情况下申请到的空间达不到地址空间上限。内存碎片会影响到你“一次”申请到的最大内存空间。比如你有10M空间,申请两次2M,一次1M,一次5M没有问题。但如果你申请两次2M,一次4M,一次1M,释放4M,那么剩下的空间虽然够5M,但是由于已经不是连续的内存区域,malloc也会失败。系统也会限制你的程序使用malloc申请到的最大内存。Windows下32位程序如果单纯看地址空间能有4G左右的内存可用,不过实际上系统会把其中2G的地址留给内核使用,所以你的程序最大能用2G的内存。除去其他开销,你能用malloc申请到的内存只有1.9G左右。
New针对数据类型的处理,分为两种情况
1> 简单数据类型(包括基本数据类型和不需要构造函数的数据类型)
2> 复杂数据类型(需要由构造函数初始化对象)
New在复杂数据类型的时候先调用operator new,然后在分配的内存上调用构造函数
从右向左入栈,从右到左的好处是,第一个参数就在栈顶,我们很方便就定位到了第一个参数的位置
网上一搜,大家都说,从右往左入栈的目的是方便的可变参数的使用,获得第一个参数的位置
1.19. 什么是类?有什么好处?(体现了面向对象的思想)
类是将不同类型的数据和这些数据相关操作封装在一起的集合体
好处:类的基本思想是数据抽象和封装,类的设计者负责考虑类的实现过程,而使用该类的程序员只需要抽象的思考类型做了什么,而不需要了解类型工作的细节,由面向过程转变为面向对象的思想
1、构造函数
特点:初始化对象所占的空间,可以重载,不依赖对象调用
2、析构函数
特点:释放对象所占的内存资源,不可以重载,依赖对象调用
3、拷贝构造函数
拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。
就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程
在c++中,下面三种对象需要调用拷贝构造函数
(1)对象以值传递的方式传入函数参数:
(2)对象以值传递的方式中从函数中返回
(3)对象需要通过另一个对象进行初始化
4、赋值运算符重载函数
拿已存在的对象给相同类型已存在的对象赋值,注意避免发生浅拷贝
四部曲:自赋值、释放旧资源、生成新资源、赋值
5、取地址操作符的重载函数
6、const修饰的取地址操作符的重载函数
C ++为成员函数提供了一个名字为this的指针,这个指针称为自引用指针。
每当创建一个对象时,系统就把this指针初始化为指向该对象,即this指针的值是当前被调用的成员函数所在的对象的起始地址
每当调用一个成员函数时,系统就自动把this指针作为一个隐含的参数传给该函数。不同的对象调用同一个成员函数时,C++编译器将根据成员函数的this指针所指向的对象来确定应该引用哪一个对象的数据成员。
虚函数:在父类中声明为virtual并在一个或者多个子类中重新定义的成员函数称为虚函数(其作用为实现多态性)
纯虚函数:类中的虚函数没有实现而被直接赋值为0(其作用是为外部提供一个接口),并且子类继承的时候,虚函数必须重写
·什么是虚函数?
2、正确的理解函数的重载、重写、重新定义。
3、虚函数是如何创建的、如何继承的?
4、虚函数是如何访问的?
virtual关键字修饰的成员函数,就是虚函数,继承父类的子类可以重写虚函数,它的作用父类的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数 ->为了实现动态多态
动态多态的实现,依靠虚函数表和动态绑定。
是一张虚函数表(vtable),由编译器编译生成并存放在某处,程序运行时在构造对象时会将该地址存放在对象中。在真正调用的时候会先通过存储在对象中的虚函数表的地址,寻找得到真正需要调用的成员函数的地址
也就是说虚函数表在编译期就已经确定 而虚函数表的地址在运行期时才确定
虚析构函数解决的问题是,因为类是动态绑定的,所以虚构的时候可能会出现 queue * a = Queue()的情况。(Queue是queue的派生。有可能派生类自己有一个指针,那总得释放吧。)
这个时候,虚析构函数不知道谁是谁,不知道该怎么析构,这个时候就需要提供一个覆写。
虚函数的生成,并不是不实现这个类了,而后面继承它的类,这个函数都是虚函数,但不一定要覆写。虚函数主要是为了动态绑定。在覆盖的过程中,形参啥的要一样,唯一可能的是,如果要返回的是自己的类型,那可以不一样。添加一个overide也可以表示这是一个虚函数。
派生类的派生过程,是一个向下降维的过程。基类的指针和引用可以用派生类的东西。
这是因为派生类的集成,是基类和派生类的组合,可以想象那个经典的图,基类是一个基座部分,派生类是一个部分。派生类在处理基类的初始化的过程,是通过调用基类的构造函数来解决的。
什么是抽象基类呢?是这个类中,有没有实现的函数,这个没有实现的函数就叫做纯虚函数。
纯虚函数的特点是什么呢?就是在其后面加上一个 = 0。
比如:double net_price(int ) = 0
虽然我们可以为net_price提供定义,但这个定义只能在类外提供,然后就算提供了,这个类也不能实现,这个类只有集成它的东西,对它进行覆写才能实现。
那么继承这个抽象基类的派生类,也可以不覆盖这个纯虚函数,但代价就是,它自己也是个抽象类,两个都不能实现,不能变成具体的对象。
那么为什么要实现这个东西呢?
因为有的时候,我们只是需要大家都实现一个相同的接口,提供相同的功能,但是,有可能这个类继承自上一个类,那么,上一个类的有些功能就仍然存在,这会造成,当前的对象也有使用这个功能的能力,可惜,我们根本不需要建立这个对象,这个对象只是中间的一环,甚至建立这个对象是有害的。所以要有个抽象基类。
虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:其一,浪费存储空间;第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的具体方式是,将基类指针指向继承类(继承类有基类的拷贝)中的基类对象的地址,但是多重继承可能存在一个基类的多份拷贝,这就出现了二义性。
类外的普通成员函数,inline函数,friend函数,static,构造
(应该尽量不要使用转换,尽量使用显式转换来代替隐式转换)
隐式转换
算术表达式隐式转换顺序为:
1、char - int - long - double
2、float - double
显式转换
被称为“强制类型转换”(cast)
C 风格: (type-id)
C++风格: static_cast、dynamic_cast、reinterpret_cast、和const_cast
static_cast
用法:static_cast < type-id > ( expression )
说明:该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。
来源:为什么需要static_cast强制转换?
情况1:void指针->其他类型指针
情况2:改变通常的标准转换
情况3:避免出现可能多种转换的歧义
它主要有如下几种用法:
用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。
用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
把void指针转换成目标类型的指针(不安全!!)
把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。
用法:dynamic_cast < type-id > ( expression )
说明:该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
来源:为什么需要dynamic_cast强制转换?
简单的说,当无法使用virtual函数的时候
用法:reinpreter_cast (expression)
说明:type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
用法:const_cast
说明:该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象
相同点:1.都是对同一种类型的数据进行储存,2.都可以用下标操作进行处理
不同点:
1.vector可以用size获取vector的长度,而数组不可以获取,在定义时就已经确定了长度
2. .vector长度不固定,可以随时增加,而数组长度固定,在定义是就不可以更改
3. .vector可以在末尾增加vector的元素(用push_back),而数组不能增加在长度以外的长度
底层结构
vector的底层结构是动态顺序表,在内存中是一段连续的空间。
list的底层结构是带头节点的双向循环链表,在内存中不是一段连续的空间。
随机访问
vector支持随机访问,可以利用下标精准定位到一个元素上,访问某个元素的时间复杂度是O(1)。
list不支持随机访问,要想访问list中的某个元素只能是从前向后或从后向前依次遍历,时间复杂度是O(N)。
插入和删除
vector任意位置插入和删除的效率低,因为它每插入一个元素(尾插除外),都需要搬移数据,时间复杂度是O(N),而且插入还有可能要增容,这样一来还要开辟新空间,拷贝元素,是旧空间,效率会更低。
list任意位置插入和删除的效率高,他不需要搬移元素,只需要改变插入或删除位置的前后两个节点的指向即可,时间复杂度为O(1)。
空间利用率
vector由于底层是动态顺序表,在内存中是一段连续的空间,所以不容易造成内存碎片,空间利用率高,缓存利用率高。
list的底层节点动态开辟空间,容易造成内存碎片,空间利用率低,缓存利用率低。
迭代器
vector的迭代器是原生态指针。
list对原生态指针(节点的指针)进行了封装。
迭代器失效
vector在插入元素时的时候,要重新给所有的迭代器赋值,因为插入元素有可能导致扩容,只是原来的迭代器失效,删除元素时当前迭代器同样需要重新赋值,否则会失效。
list在插入元素的时候不会导致迭代器实现,删除元素的时候指挥导致当前迭代器失效,其他的迭代器不会受到影响。
使用场景
vector适合需要高效率存储,需要随机访问,并且不管行插入和删除效率的场景。
list适合有大量的插入和删除操作,并且不关心随机访问的场景
由于vector的内存占用空间只增不减,如果需要空间动态缩小,可以考虑使用deque。如果非vector不可,可以用swap()来帮助你释放内存
vector().swap(nums)
swap()是交换函数,使vector离开其自身的作用域,从而强制释放vector所占的内存空间
第一级配置器
以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重新配置等操作,并且能在内存需求不被满足的时候,调用一个指定的函数。
第二级配置器
在STL的第二级配置器中多了一些机制,避免太多小额区块造成的内存碎片,小额区块带来的不仅是内存碎片,配置时还有额外的负担。区块越小,额外负担所占比例就越大。
如果要分配的区块大于128bytes,则移交给第一级配置器处理。
如果要分配的区块小于128bytes,则以内存池管理(memory pool),每次配置一大块内存,并维护对应的16个空闲链表(free-list)。下次若有相同大小的内存需求,则直接从free-list中取。如果有小额区块被释放,则由配置器回收到free-list中。
1.空闲链表的设计
这里的16个空闲链表分别管理大小为8、16、24…120、128的数据块。这里空闲链表节点的设计十分巧妙,这里用了一个联合体既可以表示下一个空闲数据块(存在于空闲链表中)的地址,也可以表示已经被用户使用的数据块(不存在空闲链表中)的地址。
2.空间配置函数allocate
首先先要检查申请空间的大小,如果大于128字节就调用第一级配置器,小于128字节就检查对应的空闲链表,如果该空闲链表中有可用数据块,则直接拿来用(拿取空闲链表中的第一个可用数据块,然后把该空闲链表的地址设置为该数据块指向的下一个地址),如果没有可用数据块,则调用refill重新填充空间。
3.空间释放函数deallocate
首先要检查释放数据块的大小,如果大于128字节就调用第一级配置器,小于128字节则根据数据块的大小来判断回收后的空间会被插入到哪个空闲链表。
Push_back参数如果是类对象或者是结构体对象的话,都会调用拷贝构造,记得在struct或者class内部,自己实现拷贝构造,从而避免浅拷贝带来的程序崩溃的情况发生,其余正常添加即可
(惨痛教训!!!)
(1)map中的元素是key-value(关键字—值)对:关键字起到索引的作用,值则表示与索引相关联的数据;Set与之相对就是关键字的简单集合,set中每个元素只包含一个关键字。
(2)set的迭代器是const的,不允许修改元素的值;map允许修改value,但不允许修改key。其原因是因为map和set是根据关键字排序来保证其有序性的,如果允许修改key的话,那么首先需要删除该键,然后调节平衡,再插入修改后的键值,调节平衡,如此一来,严重破坏了map和set的结构,导致iterator失效,不知道应该指向改变前的位置,还是指向改变后的位置。所以STL中将set的迭代器设置成const,不允许修改迭代器的值;而map的迭代器则不允许修改key值,允许修改value值。
(3)map支持下标操作,set不支持下标操作。map可以用key做下标,map的下标运算符[]将关键码作为下标去执行查找,如果关键码不存在,则插入一个具有该关键码和mapped_type类型默认值的元素至map中,因此下标运算符[ ]在map应用中需要慎用,const_map不能用,只希望确定某一个关键值是否存在而不希望插入元素时也不应该使用,mapped_type类型没有默认值也不应该使用。如果find能解决需要,尽可能用find。
https://blog.csdn.net/cmdxly/article/details/126301683?spm=1001.2014.3001.5501
https://www.cnblogs.com/zhizhan/p/4876093.html
https://blog.csdn.net/qq_32619837/article/details/89145778
https://blog.csdn.net/AngelDg/article/details/111825900
typedef:如果放在所有函数之外,它的作用域从它定义开始直到文件尾,如果放在某个函数里,作用域就是从定义开始直到该函数结尾
define:无论在函数内,还是在所有函数外,作用域都是从定义开始到文件尾
引用可以实现多态
因为多态和继承联系在一起,而引用本意是起别名,给子类对象起别名后调用函数,其效果和指针相同(一定要看懂虚函数实现多态的方式 可以对比着来答)
代码:
数组的引用是可以的:前提是如果当作参数传递的话,编译器不会将其转化成指针,而是传递数组引用本身,在这种情况下,数组的大小成为形参和实参的一部分,编译器检查数组大小与形参大小是否匹配,如果是直接在函数内写引用,可直接使用,数组的引用就是数组的一个别名
引用的数组是不允许的:int a[5] = {1,2,3,4,5}; int &b[5] = a;编译不通过,即使对数组内的引用进行初始化也不行
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
strcpy_s比strcpy多出了对目标缓冲区的检测功能
但为了增加灵活性如支持链式表达,可以附加返回值。,即可以将strcpy当成另一个函数的参数
返回值>0 代表串1大于串2
注:(VS2012Document)如果源和目标重叠,则memcpy的行为是未定义的。使用memmove处理重叠区域。【实现时注意目标-源重叠情况】使用memmove 当dst <= src用memcpy的来 当dst >= src 从后向前拷贝
C:
extern c就是链接指示
野指针指向一个已经删除的对象,或者未申请访问受限内存区域的指针
与空指针不同,野指针无法通过简单的判断是否为NULL
野指针来源:1.指针变量未初始化(指向受限内存)2.指针释放之后未置空3.指针操作超越变量作用域
strlen是计算到\0为结尾,sizeof是确定结构大小在编译器就已经确定
容器(containers)、迭代器(iterators)、空间配置器(allocator)、配接器(adapters)、算法(algorithms)、仿函数(functors)六个部分。
1> 标准消息:除WM_COMMAND外,所有以WM开头的消息,从cwnd派生的类都可以接受
2> 命令消息:来自菜单,加速键或者工具栏按钮的消息
3> 通告消息:由控件产生的消息
标准消息、命令消息和通告消息
const是由编译器处理的,提供类型检查和作用域检查。
const是只读变量,本质上还是变量,是变量就可以传递参数,而const还做类型检查,所以好处更多,如:做形参,可以接收不同的参数,更灵活。
由于是只读变量,因此保护了外面的实参,外面传递实参进来,在函数体里不能修改。因此让外面的实参得到安全性考虑。
const整个编译时间少,但是程序运行速度慢点了,因为要找内存空间开辟变量…
#define是由预处理处理,单纯的文本替换
#define只做字面上的直接替换,全局都有效,所以无论定义在哪里,全局都可以访问。在以后新的函数中不小心很可能会被替换掉,这就是为什么用它定义常量都基本上全部大写,而变量都弄成小写,这样既然不记得有多少宏名了,也不至于冲突。而const因为有作用域限制,解决了污染全局变量的困扰。
特点:宏替换的方式,让整个编译过程变慢(预编译时间+真正编译的时间),但是让程序运行速度变快,因为早已直接替换好了(宏展开),直接运行就得了
struct其实是从C语言中过渡而来 在C语言中只是作为一个存储结构而存在 在c++中扩充了很多的功能
1.默认的继承访问权。class默认的是private,strcut默认的是public。
2.默认访问权限:struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的。
3.“class”这个关键字还用于定义模板参数,就像“typename”。但关建字“struct”不用于定义模板参数