strcpy:将由source指针指示的C 字符串(包括结尾字符)复制到destination指针指示的区域中。该函数不允许source和destination的区域有重叠,当src指针指向为‘\0’时将会停止字符串的复制,同时,为了避免溢出,destination区域应该至少和source区域一样大。 strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符;(是个深复制)
strncpy:复制source的前num字符到destination。如果遇到null字符(’\0’),且还没有到num个字符时,就用(num - n)(n是遇到null字符前已经有的非null字符个数)个null字符附加到destination。注意:并不是添加到destination的最后,而是紧跟着由source中复制而来的字符后面。下面举例说明:
memcpy:将source区域的前num个字符复制到destination中。该函数不检查null字符(即将null字符当作普通字符处理),意味着将复制num个字符才结束。该函数不会额外地引入null字符,即如果num个字符中没有null字符,那么destination中相应字符序列中也没有null字符。同strcpy的区别:允许将source中null字符后面的字符也复制到destination中,而strcpy和strncpy则不可以。
重载(Overloading):函数名相同,函数的参数个数、参数类型或参数顺序三者中必须至少有一种不同。函数返回值的类型可以相同,也可以不相同。发生在一个类内部。
重写(override):也叫做覆盖,一般发生在子类和父类继承关系之间。子类重新定义父类中有相同名称和参数的 虚函数。
重定义:也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 (参数列表可以不同 ) ,指派生类的函数屏蔽了与其同名的基类函数。发生在继承中。
1、 被重写的函数不能是static的。必须是virtual的
2 、重写函数必须与被重写函数有相同的类型,名称和参数列表,返回的类型,否则不能称其为重写而是重载。
3 、重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public,protected也是可以的
1)、必须具有不同的参数列表;
2)、可以有不同的返回类型,只要参数列表不同就可以了;
3)、可以有不同的访问修饰符;
4)、可以抛出不同的异常;
a 、如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。
b 、如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏(如果相同有Virtual就是重写覆盖了)。
(1)普通的变量: 一般不考虑啥效率的情况下 可以在构造函数中进行赋值。考虑一下效率的可以再构造函数的初始化列表中进行。
(2)static 静态变量: 在类内部声明,但是必须 在类的外部进行定义和初始化。 static变量属于类所有,而不属于类的对象,因此不管类被实例化了多少个对象,该变量都只有一个。
(3)const常量:常量在类内部声明,但是定义和初始化只能在构造函数的初始化列表进行。 (const常量需要在声明的时候即初始化)
(4)Reference 引用型变量(也就是&): 引用型变量和const变量类似,需要在创建的时候即进行初始化。也是在初始化列表中进行。但需要注意用Reference类型。但是可以在类内部初始化的。
总结起来,可以初始化的情况有如下四个地方:
2、在类的构造函数初始化列表中, 包括const对象和Reference对象。
3、在类的定义之外初始化的,包括static变量。因为它是属于类的唯一变量。
4、普通的变量可以在构造函数的内部,通过赋值方式进行。当然这样效率不高。
28.(1)(2)构造函数为什么不能是虚函数
基类部分在派生类部分之前被构造,当基类构造函数执行时派生类中的数据成员还没被初始化。用一个基类指针指向一个子类对象,子类对象会先调用基类的构造函数,如果此时基类构造函数中的虚函数调用被解析成调用派生类的虚函数,而派生类的虚函数中又访问到未初始化的派生类数据,将导致程序出现一些未定义行为和bug。(同理析构函数中也是不能调用虚函数的,例如基类的析构函数中调用虚函数,派生类的析构函数在函数退出时先被调用,此时派生类已经没有内存资源了,再去调用基类的析构函数,此时如果析构函数中的虚函数被解析成派生类中的函数,也是不存在的。)
(2)虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
总结起来就是: 如果构造函数是虚函数,那么: 先要有虚函数表----->才能有虚构造函数; 但是问题在于???? 对象必须靠构造函数才能实现实例化------>实例化后才能有内存去存虚函数表; 这样实现的顺序完全相反了!所以不行!!!
另外个原因是:虚函数的作用在于通过父类的指针调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针去调用,因此就规定构造函数不能是虚函数。
29. 指针函数和函数指针
指针函数:本质是一个函数。函数返回类型是某一类型的指针
函数指针:是指向函数的指针变量,即本质是一个指针变量。
30.C++函数模板底层实现原理
函数模板:实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
使用函数模板能减少不必要的重复
不使用的话会重复定义函数
函数模板和普通函数的区别:函数模板是不允许自动类型转换的,而普通函数允许自动类型转换
当函数模板和普通函数在一起时,调用规则如下:
(1)函数模板可以像普通函数一样被重载
(3)如果函数模板可以产生一个更好的匹配,那么选择模板
(4)可以通过空模板实参列表的语法,限定编译器只通过模板匹配(max<>(a,b); //显示使用函数模板方法,则使用<>空的类型参数化列表)
(5)函数模板不提供隐式的类型转换,必须是严格的匹配。
函数模板底层的实现模式:
编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
如果 func 函数能顺利执行到 delete 处当然是最理想的。如果由于某种原因导致函数在中途返回了,或者在还没执行到 delete 语句时发生了异常,那么就悲剧了,为指针pt分配的内存得不到释放,产生了一个经典的内存泄漏。如果 func 函数是一个执行频率较高的函数,那么就尴尬了…为了消除传统的内存分配方式上存在的隐患,C++提供了一些强大的智能指针模版类,
智能指针的核心思想是:将堆对象的生存期用栈对象(这个栈对象就是智能指针)来管理,当new一个堆对象的时候,立刻用智能指针来接管;具体做法是:在构造函数中对资源初始化(用一个指针指向堆对象),在析构函数中调用delete对资源进行释放。由于智能指针本身就是一个栈对象,它的作用域结束的时候,自动调用析构函数,从而调用了delete释放了堆对象。-------->这样一个堆对象的生存期就由栈对象来管理了。
智能指针:就是智能/自动化的管理指针所指向的动态资源的释放。它是一个类,有类似指针的功能。
从较浅的层面看,智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决
智能指针典型的有着这四种:
1. auto_ptr类 (独占所有权,转移所有权 ,C++98引入的,带有很大缺陷不建议使用)
(1)auto_ptr没有使用引用计数,如果多个auto_ptr指向同一个对象,就会造成对象被删除一次以上的错误。因此一个对象只能由一个auto_ptr所拥有,在给其他auto_ptr赋值的时候,会转移这种拥有关系。所以,在赋值、参数传递的时候会转移所有权,因此不要轻易进行此类操作。
2. (独占所有权,防拷贝)
scoped_ptr的实现原理是防止对象间的拷贝与赋值。具体实现是将拷贝构造函数和赋值运算符重载函数设置为保护或私有,并且只声明不实现,并将标志设置为保护或私有,防止他人在类外拷贝,简单粗暴,但是也提高了代码的安全性。
unique_ptr “唯一 ” 拥有其所指对象,同一时刻只能有一个unique_ptr实例指向给定对象(通过禁止拷贝、只有移动所有权move()函数来实现)。在出现异常的情况下,动态资源能得到释放。
unique_ptr本身的生命周期:从unique_ptr实例对象创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。
shared_ptr允许多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
shared_ptr 的实现原理是通过引用计数来实现,智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象指针指向同一对象,拷贝或赋值时将引用计数加1,析构时只有当引用计数减到0才释放空间,否则只需将引用计数减1即可.
count)。智能指针类将一个计数器与类实例指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。每次创建类的新对象时,初始化指针就将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。
存在循环引用的时候会出现内存泄漏。
它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少.
智能指针应用在多线程的时候要注意事项:
在多线程环境下,引用计数的更新存在安全隐患-------我们可以在改变引用计数的时候加上一把互斥锁,防止多线程带来的隐患
32. 保护继承和私有继承,虚继承
虚继承:是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。
虚拟继承与普通继承的区别:
因此虚继承可以认为不是一种继承关系,而可以认为是一种组合的关系。
33. 可继承的类的实现需要注意什么问题(构造函数、析构函数)
(1) 当一个类作为基类时,它的析构函数应该为。防止基类指针无法调用子类析构函数,造成内存泄漏
(2)在子类调用父类构造函数的时候,如果父类没有默认的构造函数,则子类的构造函数应当显式地调用父类的自定义构造函数;换句话说:子类在构造时,如果没有显式调用父类的构造函数,会默认先调用父类的默认构造函数,如果此时父类没有默认的构造函数,就会报错了。
子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式;例如图中Base( a )对基类构造函数初始化了
在中,构造函数不会自动继承,只是如果子类没有写构造函数,那么系统会这个类自动添加一个,是一个空函数体,所以什么也没有做,接着就会调用父类的构造函数,再调用这个系统添加的子类默认构造函数。(但是很特殊的一点是,C++11里面 通过提供一条注明了直接基类的using声明语句来让派生类“继承”基类的构造函数的方法,这种方法要和常规的继承而来的方法区别开来。
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
实现原理:利用stack上的临时对象生命期是程序自动管理的这一特点,将我们的资源释放操作封装在一个临时对象中。
RAII 的一般做法是这样的:在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
(1)不需要显式地释放资源。
(2)采用这种方式,对象所需的资源在其生命期内始终保持有效。
资源管理技术的关键在于:要保证资源的释放顺序与获取顺序严格相反。
35. 深拷贝和浅拷贝
深拷贝(值拷贝):拷贝的是内容,主要是看复制过程的时候,如果资源重新分配,这个过程就是深拷贝
浅拷贝(位拷贝):拷贝的是地址,没有重新分配资源,就是浅拷贝
36. 拷贝构造函数什么时候需要重写
(1)当构造函数涉及到动态存储分配空间时,要自己写拷贝构造函数,并且要深拷贝。
(2)如果你需要定义一个非空的析构函数,那么,通常情况下你也需要定义一个拷贝构造函数。
placement new :placement new允许在一个已经分配好的内存中构造一个新的对象。 在使用时需要我们传入一个指针,此时会在该指针指向的内存空间构造该对象,该指针指向的地址可以是堆空间,栈空间,也可以是静态存储区。
使用new操作符分配内存需要在堆中查找足够大的剩余空间,这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题,我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。
可以使用堆的空间,也可以使用栈的空间,所以分配方式有如下两种:
这里的 new才是placement new,其实是没有申请内存的,只是调用了构造函数,返回一个指向已经分配好的内存的一个指针,所以对象销毁的时候不需要调用delete释放空间,但必须调用析构函数销毁对象。
一旦这个对象使用完毕,你必须显式的调用类的析构函数进行销毁对象。但此时内存空间不会被释放,以便其他的对象的构造。
如果缓冲区在堆中,那么调用delete[] buf;进行内存的释放;如果在栈中,那么在其作用域内有效,跳出作用域,内存自动释放。
对象池------通过对象复用的方式来避免重复创建对象,它会事先创建一定数量的对象放到池中,当用户需要创建对象的时候,直接从对象池中获取即可,用完对象之后再放回到对象池中,以便复用。这种方式避免了重复创建耗时或耗资源的大对象,大幅提高了程序性能
39. 函数模板与类模板区别,函数模板与模板函数,类模板与模板类
(1)函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化 必须由程序员在程序中显式地指定。 即函数模板的实例化允许隐式调用和显式调用 ,而类模板只能显示调用;
函数模板的隐式调用和显示调用,MaxValue是个函数
函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型。这个通用函数就称为函数模板(Function Template)。
在函数模板中,数据的值和类型都被参数化了,发生函数调用时编译器会根据传入的实参来推演形参的值和类型。换个角度说,函数模板除了支持值的参数化,还支持类型的参数化。
一但定义了函数模板,就可以将类型参数用于函数定义和函数声明了。说得直白一点,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。
类模板在实例化时必须显式地指明数据类型,编译器不能根据给定的数据推演出数据类型
类模板在实例化时必须显式地指明数据类型,编译器不能根据给定的数据推演出数据类型
注意:模板头和类头是一个整体,可以换行,但是中间不能有分号。
一但声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。
(2)函数模板的重点是模板。表示的是一个模板,专门用来生产函数。(函数模拟板的实例化允许隐式调用和显式调用 )
(3)类模板的重点是模板。表示的是一个模板,专门用于产生类的模子。(类模板的实例化 必须由程序员在程序中显式地指定)
40. 定义全局变量需要注意哪些
(1)在程序中定义了全局变量,但是需要在定义之前使用的话:这时在定义之前用extern关键字对其进行声明
(2)在一个cpp文件中定义了全局变量,需要在其他文件中使用:这时需要在其他文件中需要使用的地方之前用extern声明下
(3)在一个cpp文件中定义了全局变量,但是仅仅需要在本文件中使用该变量:这是需要在定义的时候加上static关键字
41. 静态库和动态库及其区别, C++部署动态库、静态库,程序如何去使用静态、动态链接库
库(library),库是写好的现有的,成熟的,可以复用的代码。
静态库:链接时会完整地拷贝到可执行文件中去,被多次使用就有多份冗余拷贝。在链接的时候,随着目标文件(.obj)一起被链接器打包到最后生成的可执行文件中的库叫静态库。Windows上的静态库是.lib文件(但和dll文件的.lib文件是不同的)。
动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序可以共用,节省内存。在可执行程序被加载到内存中执行的时候,才会去加载的库叫做动态库。Widows上的动态库是dll文件(Dynamic Linked Library)。
静态库与动态库的区别:
1)静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
2)使用动态库系统只需载入一次动态库,不同的程序可以共享内存中相同的动态库的副本,相比于使用静态库节省了很多内存;而使用了静态库的程序会有多个副本在内存中时,它们所使用的库所占的内存也是多份,因此浪费空间。
3)在库需要升级的时候,使用动态库的程序只需要升级动态库就好(假设接口不变),而使用了静态库的程序则需要升级整个程序。
(2) C++部署动态库、静态库,
在Windows操作系统中,Visual Studio使用lib.exe作为库的管理工具,负责创建静态库和动态库。
在静态库情况下,函数和数据被编译进一个二进制文件(通常扩展名为*.LIB), Visual C++的编译器在处理程序代码时将从静态库中恢复这些函数和数据并把他们和应用程序中的其他模块组合在一起生成可执行文件。这个过程称为"静态链接",此时因为应用程序所需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行。
在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。
Window与Linux执行文件格式不同,在创建动态库的时候有一些差异:
在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数作为初始化的入口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。
Linux下gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要函数做特别的声明,编写比较方便。
(3)程序如何去使用静态、动态链接库
动态链接库的使用和静态链接库类似,唯一不同的是需要将myStaticLibTest.dll文件和项目编译生成的执行文件:.exe文件放在一起,一般位于解决方案的debug或release文件夹中。
vs中的程序有debug和release两个版本,Debug通常称为调试版本,通过一系列编译选项的配合,编译的结果通常包含调试信息,而且不做任何优化,它为开发人员提供强大的应用程序调试能力。而Release通常称为发布版本,是为用户使用的,一般客户不允许在发布版本上进行调试。所以不保存调试信息,同时,它往往进行了各种优化,以期达到代码最小和速度最优。为用户的使用提供便利。
debug程序通常比release程序要慢,尤其是处理视频图像方面release要比debug快很多。在release模式对程序进行调试的时候经常会遇到变量虽然初始化了,但是在查看其值的时候却发现是一个随机的数并不是初始化的值,有时候在对变量进行监视的时候了,会出现找不到变量的情况,原因大致如下:
而release的赋值近似于随机。如果你的程序中的某个变量没被初始化就被引用,就很有可能出现异常:用作控制变量将导致流程导向不一致;用作数组下标将会使程序崩溃;更加可能是造成其他变量的不准确而引起其他的错误。所以在声明变量后马上对其初始化一个默认的值是最简单有效的办法,否则项目大了你找都没地方找。代码存在错误在debug方式下可能会忽略而不被察觉到。debug方式下数组越界也大多不会出错,在release中就暴露出来了,这个找起来就比较难了。
只有DEBUG版的程序才能设置断点、单步执行、使用 TRACE/ASSERT等调试输出语句。REALEASE不包含任何调试信息,所以体积小、运行速度快。
43. (1) main函数有没有返回值,分别针对什么情况?(2) main函数中的参数argc和argv含义及用法?(3) 如果出现异常,怎么捕获?(4)main函数一定是最先执行的吗?
VS环境下mian函数的返回值可以是数值类型,如char,float,double或者long,但是绝不能是string这类不能强制转换成int的类型(VS环境下对main函数的返回值没有太严格的要求,只要可以强制转换为int的类型都可以作为返回值 )
argv 是 argument vector的缩写,表示传入main函数的参数序列或指针,并且第一个参数argv[0]一定是程序的名称,并且包含了程序所在的完整路径,所以确切的说需要我们输入的main函数的参数个数应该是argc-1个;
块抛出的任何类型的异常)除非try里面执行代码发生了异常,否则这里的代码不会执行 }
第三点: throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
(4)实际上,所有的外部对象的构造函数都是先于main()函数执行的。如果要对类中的成员对象进行初始化,那么这些对象的构造函数也是在main()函数之前执行的。如果在这些构造函数中还调用了其他函数的话,就可以是更多的函数先于main()函数之前运行。因此main()函数不一定是C++程序的第一个被执行的函数。
44. C++写的动态链接库能不能直接给C用,为什么?
动态链接库(Dynamic Link Library, dll):其实dll文件就是一堆函数的集合,我们只不过是给普通函数的定义加点东西罢了
最前面的 extern "C" 是为了防止不同编译器间的差异的,它的意思是以C语言方式命名,作用是让编译器知道要以C语言的方式编译和连接封装函数。 其中_declspec为什么要加这个条件编译呢?
因为这种技术也可能会用在由C头文件产生出的C++文件中,这样使用是为了建立起公共的C和C++文件,也就是保证当这个文件被用做C文件编译时,可以去掉C++结构,也就是说,extern "C"语法在C编译环境下是不允许的。
(1)用C++去调用C动态链接库:(不能直接去调用)
就可以让C++程序在进行调用的时候进行正确的编译--------在CDLL(用C写的动态链接库)中函数当然是使用C编译器的方式进行编译的,所以在调用程序中,在声明外部函数的时候,必须加上”C”,以使的这个C++程序,在编译的时候使用C编译的方法对这个外部函数声明进行编译,即直接使用函数名而不是一个经过处理的函数名,否则在编译运行的时候就会报链接错误。比如一个函数void fun(double d),C语言会把它编译成类似_fun这样的符号,C链接器只要找到该函数符号就可以链接成功,它假设参数类型信息是正确的。而C++会把这个函数编译成类似_fun_double或_xxx_funDxxx这样的符号,在符号上增加了类型信息,这也是C++可以实现重载的原因。
(2)用C去调用C++动态链接库:(不能直接去调用)
主要的思想就是将C++的动态库再封装一层,做一个中间接口库,对C++库进行二次封装,也就是说在封装的这一层编写C语言的函数接口,而接口中使用C++动态库提供的类;还是用到 extern "C"
45. 设计一个模板函数对常数类型输入返回1(深入------函数和类模板特例化,偏特化)
这里再进一步介绍 函数模板特例化 和 类模板的特例化与偏特化
特例化版本时,函数参数类型必须与先前声明的模板中对应的类型匹配,其中T为const char*。
(1)函数模板特例化:必须为原函数模板的每个模板参数都提供实参,且使用关键字template后跟一个空尖括号对<>,表明将原模板的所有模板参数提供实参。
本质:特例化的本质是实例化一个模板,而非重载它。特例化不影响参数匹配。参数匹配都以最佳匹配为原则。例如,此处如果是compare(3,5),则调用普通的模板,若为compare(“hi”,”haha”)则调用特例化版本(因为这个cosnt char*相对于T,更匹配实参类型),注意,二者函数体的语句不一样了,实现不同功能。
注意:普通作用于规则使用于特例化,即,模板及其特例化版本应该声明在同一个头文件中,且所有同名模板的声明应该放在前面,后面放特例化版本。
(2)类模板特例化:原理类似函数模板,不过在类中,我们可以对模板进行特例化,也可以对类进行部分特例化。对类进行特例化时,仍然用template<>表示是一个特例化版本
按照最佳匹配原则,若T != sales_data,就用普通类模板,否则,就使用含有特定功能的特例化版本。
类模板的部分特例化:不必为所有模板参数提供实参,可以指定一部分而非所有模板参数,一个类模板的部分特例化本身仍是一个模板,使用它时还必须为其特例化版本中未指定的模板参数提供实参。此功能就用于STL源码剖析中的traits编程。详见C++primer 628页的例子。(特例化时类名一定要和原来的模板相同,只是参数类型不同,按最佳匹配原则,那个最匹配,就用相应的模板)
特例化类中的部分成员:可以特例化类中的部分成员函数而不是整个类。
左值:指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式),指的是如果一个可以引用到某一个对象,并且这个对象是一块内存空间且可以被检查和存储,那么这个表达式就可以作为一个左值。
右值:指的则是只能出现在等号右边的变量(或表达式),指的是引用了一个存储在某个内存地址里的数据。
a,b为左值,3,4为右值
47. cpp大规模对象分配,生命周期控制的难点(并发情况下)?
智能指针控制生命周期,小对象可以参考java的Spring直接搞成只读的,配合 (只有进程空间的各段的内容要发生变化时,才将父进程的内容复制一份给子进程)和原子操作尽量把临界区弄小;大规模内存分配的解决方案,go语言的tcmalloc参考,给每个线程弄自己的内存缓存区。
48. 如何用让cpp程序输出他本身的代码
fstream提供了三个类,用来实现c++对文件的操作。(文件的创建、读、写)
(1)>>操作符会忽略前面的空白符和换行符,但不会越过后面的换行符和空白符
(2)get()方法不会略过任何符号
文件文件打开模式:
ios::nocreate 打开一个文件时,如果文件不存在,不创建文件。
ios::noreplace 打开一个文件时,如果文件不存在,创建该文件
ios::trunc 打开一个文件,然后清空内容
常用的错误判断方法:
(1)good() 如果文件打开成功;(2)bad() 打开文件时发生错误;(3)eof() 到达文件尾
49.源代码到最后的可执行文件经历的过程
1)预处理主要包含下面的内容:
a.对所有的“#define”进行宏替换;
c.处理“#include”指令,这个过程是递归的,也就是说被包含的文件可能还包含其他文件
d.删除所有的注释“//”和“/**/”
e.添加行号和文件标识
f.保留所有的“#pragma”编译器指令
经过预处理后的 .i 文件不包含任何宏定义,因为所有的宏已经被替换,并且包含的文件也已经被插入到 .i 文件中。
编译的过程就是将预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件(.s文件)
汇编器是将汇编代码转变成机器可以执行的代码,每一个汇编语句几乎都对应一条机器指令。最终产生 目标 文件 (.o文件或.obj文件)。
静态链接把要调用的库函数直接链接到目标程序,成为可执行文件的一部分。换句话说,要调用的库函数在程序的exe文件中,该文件包含了运行时所需的全部代码。静态链接的缺点是当多个程序都调用相同的函数时,内存中会有多个这个函数的拷贝,所以浪费了内存资源。
动态链接所调用的库函数代码并没有拷贝到程序的可执行文件中,它仅仅在exe文件中加入了调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,仅当应用程序被装入内存开始运行时,才从DLL中寻找相应函数代码,因此需要相应DLL文件的支持
51.虚函数表会随着对象生成而复制吗?
不会的,包含虚函数的类才会有虚函数表, 同属于一个类的对象共享虚函数表, 但是有各自的_vptr.
虚函数表实质是一个指针数组,里面存的是虚函数的函数指针。
我们知道在C语言中是没有class类这个概念的,但是有struct结构体,我们可以考虑使用struct来模拟;但是在C语言的结构体内部是没有成员函数的,如果实现这个父结构体和子结构体共有的函数呢?我们可以考虑使用函数指针来模拟。但是这样处理存在一个缺陷就是:父子各自的函数指针之间指向的不是类似C++中维护的虚函数表而是一块物理内存,如果模拟的函数过多的话就会不容易维护了。
53.memset函数的作用,有哪些参数
总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。
memset:作用是在一段内存块中填充某个给定的值,它是对较大的或进行清零操作的一种最快方法
volatile 和 const 是相对应的,const代表所修饰的变量是不变的,volatile代表所修饰的变量是随时都可能会变化的。
volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说
volatile 可以保证对特殊地址的稳定访问。注意,在 VC 6 中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。
55.如何让C++类不能被继承
(1)利用static 属性定义静态方法,在方法内部实现一个对象,然后返回它的指针
(2)将构造函数和析构函数设置成私有或者保护属性