41.隐式类型转换和强制类型转换
隐式类型转换是指当赋值或比较或加减等运算时,char->int, int->un_long int->float float->double.或者右值被转换为左值的类型。
●在混合表达式中,其操作数被提升为存储空间最大的类型。
●用作if,while等条件表达式的值都被转化为bool型。
●将一个表达式赋值给某个变量时,表达式的类型被隐式转换为该变量的类型
●数组作为函数的参数时就会自动转换为相应的指针类型。
强制类型转换:
●非const类型的变量可以不用强制就转换为相应的const类型,但是const类型的变量必须用显示的强制类型转换才能变为非const类型。
●存储空间大的变量强制转换为存储空间小的变量时会丢失精度或丢失高位
比如:int a = 65500;
unsigned char p = (Unsigned char)a;
Int型变量a是4字节,char型是1字节,65500对应的十六进制数会丢掉高3字节,只保留低1字节赋值给p。
●存储空间相同的变量直接进行强制类型转换不会有问题,例如
Sockaddr与sockaddr_in之间的转换就是这样的。
●void *和具体的指针之间不需要进行强制类型转换。
42.关于memcpy(void *d, const void *s, count)和memmove(void *d, const void *s, count)区别
和书上说的不同,我的试验结果是他们在int*和char*情况,不关事d<s+count
还是s<d+count的内存覆盖情况,两个函数的结果都是相同的。都会
修改s的内容,但在修改前都会复制出该变量。
但是另一个值得注意的问题是,在有关mem之类函数时,count的值指的是最小存储单位1个字节,如果指针不是cha型指针时,count应该用如下的赋值方法:
Count = sizeof(int)*n;这样才能达到希望的效果。
43.关于数组的内存分配问题
数组的内存分配属于在栈上的内存分配。
在C语言中,对于多维数组,采取以行为优先的存储结构。也就是数组a[2][3]
在内存中的存储顺序时a[0][0],a[0][1],a[0][2],a[1][0],a[1][1],a[1][2].在其他语言中有可能采用以列优先的存储结构。
例如:float a[5][4]在内存中的首地址为2000 Byte,已知float型变量在系统中占用4个字节(32bit),问a[3][2]在内存中的地址时多少?A[m][n]
Addr = Source + (i*n + j)*sizeof(type);
Addr = 2000 + (3*4 + 2)*4 = 2056 Byte
44.指针的引用作为函数的参数
例如:typedef char* pch;
char *p;
Test(p);
/
Void test(pch &pp)
{}
这和指针作为函数参数的区别在于:指针作为参数和指针的引用作为参数,当对指向的内容修改时,会起到相同的作用,即都会修改内存的内容。不同的是,指针的引用作为参数时对指针本身的指向做修改是也会影响到原来的指针。例如在被调函数中把pp=NULL会使得主函数中的指针p=NULL。
45.函数名、函数指针、函数名作为函数的参数
就象某一数据变量的内存地址可以存储在相应的指针变量中一样,函数的首地址也以存储在某个函数指针变量里的。这样,我就可以通过这个函数指针变量来调用所指向的函数了。实际上,函数名是一个指针常量,而函数指针是一个指针变量。既然函数指针是一种指针变量当然可以作为函数的参数。用法举例如下:
void MyFun1(int x);
void MyFun2(int x);
void CallMyFun(void (*fp)(int), int x);
int main(int argc, char* argv[])
{
CallMyFun(MyFun1, 10);
CallMyFun(MyFun2, 20);
}
void CallMyFun(void (*fp)(int), int x) //
{
fp(x);//
}
void MyFun1(int x) //
{
printf(“函数MyFun1中输出:%d/n”,x);
}
void MyFun2(int x)
{
printf(“函数MyFun2中输出:%d/n”,x);
}
/
可以看出:函数名作为函数的参数时,可以借助一个函数调用具有相同格式(函数的名字可以不同,函数的参数要相同,函数的返回值要相同)的不同函数。
46.结构体的数据对齐问题
结构体的sizeof这是初学者问得最多的一个问题,所以这里有必要多费点笔墨。让我们先看一个结构体:
struct S1
{
char c;
int i;
};
问sizeof(s1)等于多少聪明的你开始思考了,char占1个字节,int占4个字节,那么加起来就应该是5。是这样吗你在你机器上试过了吗也许你是对的,但很可能你是错的!VC6中按默认设置得到的结果为8。
为什么需要字节对齐计算机组成原理教导我们这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,以此类推。
这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。让我们交换一下S1中char与int的位置:
struct S2
{
int i;
char c;
};
看看sizeof(S2)的结果为多少,怎么还是8再看看内存,原来成员c后面仍然有3个填充字节,这又是为什么啊别着急,下面总结规律。
字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);成员是结构体时,结构里有则结构体成员要从其内部最大元素大小的整数倍地址开始存储。
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
例子:
#include <stdio.h>
struct foo
{
char c1;//0x00000000
short s;//0x00000002
char c2;//0x00000004
int i;//0x00000008
};
struct bar
{
char c1; //0x00000000
char c2;//0x00000001
short s;//0x00000002
int i;//0x00000004
};
#pragma pack(1)//以1或者数据成员大小m这两者中最小的一个数的倍数对齐
struct foo_pack
{
char c1;//0x00000000
short s;//0x00000001
char c2;//0x00000003
int i;//0x00000004
};
#pragma pack()
int main(int argc, char* argv[])
{
char c1; //栈上的内存一定是以4对齐的,//0x0000000c
short s; //0x00000008
char c2; //0x0000004
int i; //0x0000000
struct foo a;//静态内存
struct bar b;
struct foo_pack p;
return 0;
}
47. 关于共用体
声明:union data
{
Int i;
Char c;
Float f;
}a;
Union data b,c;
数据使用:
仅仅使用共用体变量是不能获得任何数据的,必须使用a.i a.f等。共用体变量也不能作为函数的参数,但是共用体的指针可以作为函数的参数。
内存的分配:
共用体的所有成员的的起始地址是相同的,其总共的大小由所有数据成员中最大的决定。在同一时刻,仅仅有一个成员能够生存。一旦有一个新的成员被赋值使用,旧的所有成员就都不在可用了。
共用体与结构体:共用体和结构体可以相互嵌套使用,之所以发明共用体是因为。在每个成员有多种互斥不同属性时,可以节省内存的使用。仅用一个共用体,每个成员就可以取得和其对应的属性了。
48.关于枚举类型
声明和定义: enum Week{sun,mon,tue,wed}weekly;
赋值:枚举类型的所有成员都是常量,不能改变,在编译的时候,系统会一次给每个成员赋值0123.。。。。 。但是枚举变量是可以改变的,但要加强制类型转换:
weekly = (Week)sun;
内存分配:枚举变量的类型和int类型占用的内存空间是相同的。
49.类函数的覆盖、隐藏和重载
| 存在的地方 | 是否有Virtual 关键字 | 函数名和参数 是否相同 |
覆盖 | 基类和派生类 | 基类中有Virtual | 函数名与参数 完全相同 |
隐藏 | 基类和派生类 | 基类中Virtual可有可无 | 函数名相同参数 可以不同 |
重载 | 同一类中 | 无 | 函数名相同 参数一定不能相同 |
隐藏的类型:派生类的函数与基类的函数完全相同,但基类中没有Virtual,这时基类的函数被隐藏;派生类的函数与基类的函数名相同,但是函数的参数不同,这时无论基类的是否有Virtual,基类的函数都将被隐藏。
覆盖的作用:函数的多态性是由虚函数实现的,因此函数的覆盖总是和多态性联系在一起的。在覆盖的情况下,编译器会在程序运行时根据对象的实际类型来确定要调用的函数。
隐藏的作用:隐藏的作用是隐藏对象的实际类型,而是根据函数形参的类型来确定调用哪个函数。指针类型决定。
覆盖的作用:在一个类中,可以定义很多个构造函数,但是每个构造函数的参数列表不同,具体调用哪一个是由对象的实际参数来决定的。
50.关于编译时找不到文件的问题
今天照着别人的例子和步骤做,用project->Add to project->files的办法,向工程中添加写好的.H和.Cpp文件。添加好后进行编译,出现错误:no such file or dictionary!
完全按照步骤做的,为什么会错呢?原因就是:没有把要包含的几个文件拷贝到工程的目录下面。
教训:当使用别人或自己已经编写好的头文件或原文件时,出了用上述方法包含到工程中,还要把所需的文件拷贝到工程的目录下才行。