首页 > 代码库 > c++ 杂

c++ 杂


1.强制类型转换:

一般使用的是c风格的类型转换,进行简单数据类型间的转换,而对于类之间的转换,使用4个转换符。

reinterpret_cast <new_type> (expression): 只用于指针和引用, 将一个指针类型强制转换为另一个指针类型,不做任何操作,仅返回转换类型后的指针,与c的强制类型转换效果相同。用于底层的强制类型转换,可将指针转换为整形。只是对底层的二进制数据换一种读法。

static_cast : 可以进行 类型定义的显示转换,和指针类型的转换。对与指针的转换,允许任意的隐式转换和相反转换动作,指其可以将基类的指针转换为派生类的指针。是最接近c风格的转换,但是不能const对象转换为非const对像。对于编译器中隐式执行任何强制类型转换都可以由static_cast显式完成。修改底层的数据,强制转换为新的数据类型。

这里reinterpret与static两种方式的区别是,前者是对原有二进制数据的重新换成新的数据类型的读法来解读,而后者则是强制类型转换,将类型中原有的二进制数据进行修改。

dynamic_cast: 只用于对象的指针和引用,其对象必须有多态属性,即有虚函数。其主要用于多态的情况,可以在执行期决定真正的类型。如果转换不安全,返回空指针。其上行转换与static_cast相同,下行转换如果不安全则返回空指针。

const_cast : 只用于指针和引用。用来设置或删除对象的常量性。



2.const类型,对应一个常量字符串或其他常量不能用来赋值非常量的引用,但是可以复制非常量的数据类型,对于string foo();和常量字符串这样的类型,会产生一个const类型的临时对象,位于全局变量区,如果是指针的话,但对与赋值给数组,则是真正将其复制给数组。

复制构造函数一般使用常引用。

对于指针,const在 *前表示这个指针指向的地址的值是常量,如果在*后标识这个指针 的值 也就是其指向的地址是常量,不能改变。


3.引用的好处:

当使用引用作为形参时,由于引用没有占用储存单元,仅仅是一个别名而已,所以这里也没有将实参赋值给形参的操作,而直接对实参进行操作。

当返回引用格式的数据,在内存中不产生返回值的副本,所以返回的引用不能指向函数内的局部变量。不能返回函数内部new分配的内存的引用,如果返回值没有被赋值,就无法释放内存。返回类成员的引用最好用const。流操作符>><<返回值为引用,也必须为引用,若为流对象需要复制一个新的对象,且操作的不是同一个对象,而对于指针无法后接<<和>>符号。+-*/返回一个新的对象,而=后返回引用。

一个基类的引用可以指向派送类实例。




4.多态:

多态是指 同一操作作用于不同类的实例,将产生不同的执行结果,即不同的类的对象收到相同的消息 ,得到不同的结果。

多态分为运行时多态和编译时多态。

编译时多态:派生类重写基类中的成员变量和成员函数,然后调用时,虽然名字相同,但效果不同。

运行时多态:使用基类指针指向派生类对象,却能正常调用派生类中重写的虚函数。

多态的作用:隐藏实现细节,使代码模块化,扩展代码模块,实现代码重用。接口重用,保证使用家族中任一实例某一属性的正确调用。



5.#include <file.h>是从Standard Library的路径查找和引用的。

而#include "file.h"是从当前文件路径搜寻并引用的、


6.extern “C"

之后可以使用c语言。对于c++,一般函数编译后名称为函数名加参数类型,如void foo(int x,int y) -> _foo_int_int,从而实现了函数重载,而c语言中编译后名称仅为函数名称,如_foo,这样c和c++的混合编程要考虑之前c的函数无法在c++语言中使用,反之同理。

一般用于:c语言和c++语言的混合编程。


7.关联,聚合和组合:UML

关联是指一般性的联系,如教师与学生就是一种关联关系。

聚合是 has_a,聚合类包含若干非聚合类。

组合是 contains_a ,组合关系强与聚合,整体由部分组成。


8.重载 overload 和重写 overried:

重载是允许多个同名函数,但这些函数的参数表不同。 重载的函数可能返回类型不同。

重写是指派生类重写基类虚函数方法。

对于 派生类中 函数返回类型相同且参数相同的对基类方法的 重定义,称为 redefining , 也称隐藏。



9.struct 和class

事实上struct里可以有函数,可以有构造函数,可以析构函数,可以有继承,可以有多态。。。。

所以struct与class只有一个区别,就是struct里全部成员的默认为public,而class中成员默认为private而已。。。


10.一个类中如果没有任何成员,则sizeof的值也不为0,编译器必须让其占有一定地址。


11.隐藏:

当派生类的函数与基类的函数同名但参数不同时,基类函数被隐藏,但这不是重载也不是覆盖。

但派生类的函数与基类的函数同名同参时,但基类函数没有virtual,即不是虚函数,此时也为隐藏。


12、一些可能会用到的函数:

绝对值函数abs。

_onexit ()  :注册 退出回调函数。


13. 判断一段程序是否用c++编译程序编译:ifdef __cplusplus.


14. vector 的原理,一开始为数组开辟较小的空间,然后向数组中添加数据,当数据的数目超过数组的容量时,再重新分配一块更大的空间,每次扩容一倍,然后将之前的数据复制到新的数组中,然后再将之前的内存释放。

这也就是我之前使用vector出错的原因,每次扩容都会删除之前的数据,而以之前数据位置的指针都将无效。。。

15.,对一个数组进行sizeof,获得的是数组的大小,而对一个指针获得大小,其大小为4,虽然一个指针与一个数组效果相同。但当一个数组作为参数传值时,数组会退化成指针,则其大小为4.

对于一个函数进行sizeof操作,其结果为对函数的返回值类型进行sizeof。

16,  对于字符串,c++中将常量字符串放在一个单独的内存区域,则如果对两个字符指针赋值同一个常量字符串时,它们实际指向相同的地址。所以当将一个常量字符串赋值给一个非常量的指针时,实际这个指针是常量的,不能修改其指向地址的数据。


17.编译器的字节对齐:32位编译器默认是4字节对齐,64位默认为8,使用 #pragma pack(int n) 来设置偏移量,n为2的倍数,当n大于struct中最大的成员变量的长度时,其偏移不以n为标准,而以最大成员变量的长度为标准,结果为n的整数倍,否则都为n的整数倍。当对齐时,如果一个变量和之后的变量都短于 对其值(最大变量长度与设置偏移值的最小值),则可以将2者写在同一行中,否则要填充空格。

如果struct内部包含另一个struct结构体,则将此结构体中的元素放入父结构体中,然后在来进行计算。而不是直接将子结构体看做一个整体。


18.排序算法的稳定性:对于序列中相等的两个元素,如果经过排序后,其相对位置保持不变,则其为稳定的,否则为不稳定的。这很大程度上跟程序中的判断条件有关。

一般来讲:快速排序,堆排序,直接选择排序是不稳定的排序。 冒泡排序,直接插入排序和两路合并排序是稳定的排序。

稳定性的好处:如果稳定,当进行多键排序时,一个键的排序结果可以为第二个键的排序所用。


19.作用域,所有用{}包含的都为作用域,作用域中的局部变量再出了作用域后,便立即释放其内存。


20. 函数 strcmp ,比较两个字符串,如果str1 == str2 返回0,如果str1 >str2返回正数,否则返回负数。


21.在c++中,成员变量的初始化顺序只与它们在类中的声明顺序有关,与初始化列表的顺序无关。。。


22. 修饰类成员函数的const ,如,void fun() const ;

const对象只能访问const函数,非const对象可以访问const函数。const对象的成员不能改变,但是指针维护的对象可以更改。

const函数不可以修改对象的属性。用mutable形容的变量可以被const函数修改。


23.模版 : template <class / typename T> 尖括号内为模版形参表。

使用模版,编译器自行判断模版实参,绑定到模版形参上,称实例化了函数模版的一个实例。原理是编译器自己产生了对应实参的类型的函数。

模版名会把typedef中的命名屏蔽。

模版类或模版函数的声明和定义要放在一个文件内。


24.main函数可以不写返回值,然后编译器会隐式返回0.


25.可以随意给指针赋予地址,但是不能修改这个地址。如可以有int *p ;p = NULL,但是不能修改*p的值。


26.函数指针为 数据类型 (*fun)(参数列表)。

指向const类型的指针为 const int * p 或 int const * p ,而指针本身值为常量的写法是 int * const p,指向一个常量的常量指针的写法为 const int * const p。

取函数指针以及使用的写法如下:

int fun(int a,int b){
	return a+b;
}

int main(){
	//int fun(int,int);
	int (*f)(int,int) = &fun;
	int a=0,b = 1;
	printf("%d",(*f)(a,b));

对于数组指针以及指针数组,用()括起来的*与指针变量结合,而之前的*与之前的数据类型结合,然后从里向外解读。则如 double* (*p)[10];p为一个指针,其指向一个 指针数组 。

int (*f[10])() ,f是一个数组,f有10个元素,其保存的元素为返回int的无参的函数。

int (* (*F)(int ,int)(int): F是一个函数指针,其指向一个有另个int参数的函数,函数返回值为一个函数指针,这个函数指针为参数为一个int,返回int的函数。


27.虚析构函数,当A为基类B为派生类时,如果使用多态的方式来生成对象如 A* a = new B();则delete a时会导致其只调用基类的析构函数,而不释放派生类申请的地址空间,使用虚 析构函数能有效的防止这种事情。对于使用基类指针来销毁对象。

构造函数不能是虚函数,因为之前的析构函数由于不清楚对象究竟是那种对象,所以使用多态的方法来使其安全,而构造函数明确指定了对象的类型。
虚函数,每个对象都有一个虚表指针,指向虚表,而在虚表中存放虚函数的地址。

虚继承时,会有一个虚类指针指向其父类的唯一对象。


28.内联函数inline,编译过程中对于调用函数,直接将代码写在调用的地方,与宏命令相似,但比宏命令更加安全 可靠。其主要作用是减少函数调用时的消耗。一般用于 一个函数经常被调用,且只有简单的几行且没有for,while,switch等语句。


29..定义一个宏,返回结构体中某个成员相对与结构体的偏移量:

#define FIND(stru,e) (size_t)&(((stru*)0)->e)
用c++中的强制转换应如下:

#define FIND(stru,e) (size_t)&((reinterpret_cast<stru*>(0))->e)
扩展,写一个宏SIZEOF来计算各种数据类型的大小:

#define SIEZEOF(stru)  (size_t)&(((stru*)0)[1])

30.地址指针与数组的一些联系:

对于一个数组 的头进行取地址,会获得一个 相应指针 的地址,即会获得一个指向这个数组的指针的指针地址:如下:

int a[2][4] = {{1,2,3,4},{2,4,5,6}};
	int *p = (int * ) (&a+1);
	int d = *(p-1);
	cout << *(p-1)<<endl;
a的值为数组的地址,或者可以认为是 指向数组第一个元素的指针 的值。而&a是将数组取其地址,获得一个指向数组的 指针的值,即在这里,&a是 一个 int (*a)[2][4],即一个指向一个二维数组的指针 的值。所以输出结果为6.


31.句柄是整个Windows编程的基础,一个对象用来标识Windows在内存中维护的一个对象 内存物理地址列表。句柄不是指针。由于内存使用虚拟地址,导致Windows维护的对象在内存中的地址和内容不是一成不变的,所以使用句柄来维护对象。


32.auto_ptr:

使用std::auto_ptr,要#include <memory>。

主要目的是简化指针释放动态空间的操作,以防内存泄漏。这是一个类,生成的是一个对象,在其析构时自动删除此指针。代码不会泄漏内存,即使函数异常,因为对象的析构函数总是在退栈过程中调用。用法:

auto_ptr<T> pt (new T);

33.深拷贝与浅拷贝:

浅拷贝:只是简单的复制类中的成员变量。

深拷贝:对于类中动态对象 重新分配空间并复制。

两者的主要区别在与对于动态申请的内存与变量,在浅拷贝中只是将新的指针指向旧的指针的地址,而在深拷贝中会重新申请一块地址并赋值。


34.面向对象的优点:良好的可复用性,易维护性,良好的可扩充性。

空类默认生成四个函数:默认构造函数,析构函数,拷贝函数,赋值函数。

类与struct只有一个区别:类中默认为private,struct 中默认是public、

对于一个类的构造函数中有参数,则可以使用以下形式新建对象:CLASSA obj( 参数);,但对于默认构造函数要使用这种形式:CLASSA obj;而不是ClassA obj();而对于语句ClassA obj(),其本身不会报错,因为这句是在声明obj为一个参数为空,返回值为ClassA类型的函数。

类内静态成员变量要在类外进行定义,而类内常量要在构造函数的初始化列表中定义,而 const static 和static const类型的数据可以在类中声明并初始化,也可以在类外定义,类外定义时要省略static关键字。

构造函数的初始化列表的初始化顺序与初始化列表的顺序无关,与成员变量在类中的声明顺序相同。

覆盖又称重写 override,是多态,只有多态下的重写基类函数才称为覆盖,而没有多态的话,只是隐藏而已。 重载称为overload,重载必须是参数不同。

对于虚继承,派生类会有一个指针指向基类,虚继承是为了多重继承的实现,当多重继承时有一个相同的基类时,不让派生类拥有重复多于的数据,使用虚基类。虚继承时,派生类会生成一个vftable的成员变量,指向虚基类表。 虚继承时,类中有基类的副本的同时多了一个指向虚基类表的指针。多重继承时对共有的虚基类只保留一份,这个虚基类值 基类使用virtual继承时的基类,也只有虚继承中的派生类中有自己有这个指向虚基类表的指针,之后派生的正常派生类只有基类数据中的虚基类指针。

c++中不定义属性的抽象类就是接口。多重继承时调用一个基类中与另一个基类中冲突的函数的方式为: obj.A::fun() 。


35.explicit关键字:声明为explicit的构造函数不能在隐式转换中使用。

对于一个类的只有一个参数的构造函数,还可以作为默认且隐含的类型转换操作符。即使用AAA == aa,aa为构造函数的唯一一个参数类型时,调用这个构造函数创建一个AAA对象,而使用explicit来阻止这种隐式调用。


36.RTTI,runtime type information ,运行时类型信息。即程序能够使用基类的指针或引用来检测这些指针或所指对象的实际派生类型。两个重要的操作符 typeid 和dynamic_cast。

typeid  :返回指针和引用所指的实际类型。使用用法:

	A* d= new D();
	cout<< typeid(d).name()<<endl;
	cout<< typeid(*d).name()<<endl;
前者获得了d现在的类型,也就是 A *,是指针的类型 ,而后者为d指向的实际内容的数据类型为 D。如果是引用, 若是d返回派生类类型,若是 &d 返回基类类型。

dynamic_cast :将基类型指针或引用安全地转换为派生类型的指针或引用。转换时会先检测能否转换,若能,则转换之返回地址,否则返回0,则使用这个可以代替typeid的操作:

	if( dynamic_cast<D*>(d) ){
		cout<<"type is D"<<endl;
	}else{
		cout<<"type is not D"<<endl;
	}
但最好不要使用RTTI,最好使用dynamic_cast 来代替 typeid.


37.重载:

对于++ 和 --,前缀为一元函数,后缀为二元函数。




c++ 杂