c/c++易错知识点整理2(预处理,const,static,sizeof)(下)
1.使用sizeof计算普通变量所占空间大小
char str[]="hello";
char *p=str;
int n=10;
sizeof(str)=___;
sizeof(p)=___;
sizeof(n)=___;
void Func(char str[100])
{
sizeof(str)=___;
}
void *p=malloc(100);
sizeof(p)=___;
代码第4行,str变量表示数组,对数组变量做sizeof运算得到的是数组占用内存的总大小。数组中要有一个元素保存字符串结束符'\0',所以sizeof(str)为6;
代码第5行和第6行,p和n分别是指针和int型变量。在32位WinNT平台下。指针和int都是4个字节。
代码第9行中的str是函数的参数,它在做sizeof运算时被认为是指针。这是因为当我们调用函数Func(str)时,由于数组是“传址”的,程序会在栈上分配一个4字节的指针指向数组。因此结果也是4
代码第11行中的p首先指向一个100字节的堆内存。这里还是对指针做sizeof运算,结果仍然是4.
2.使用sizeof计算类对象所占空间大小
#include <iostream>
class A
{
public:
int i;
};
class B
{
public:
char ch;
};
class C
{
public:
int i;
short j;
};
class D
{
public:
int i;
short j;
char ch;
};
class E
{
public:
int i;
int ii;
short;
char ch;
char chr;
};
class F
{
public:
int i;
int ii;
int iii;
short j;
char ch;
char chr;
};
32位WinNT
sizeof(A)=4sizeof(B)=1
sizeof(C)=8
sizeof(D)=8
sizeof(E)=12
sizeof(F)=16
这里考到对齐的概念
对齐需要满足3个准则:
1.结构体变量的首地址能够被其最宽基本类型成员的大小所整除
2.结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要,编译器会在成员之间加上填充字节
3.结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节。
3.使用sizeof计算含有虚函数的类对象的空间大小
普通函数不占用内存,只要有虚函数,就会占用一个指针大小的内存,原因是系统多用了一个指针维护这个类的虚函数表,并且注意这个虚函数无论含有多少项(类中含有多少个虚函数)都不会影响类的大小。
4.使用sizeof计算虚拟继承的类对象的空间大小
#include <iostream>
using namespace std;
class A
{
};
class B
{
};
class C:public A,public B
{
};
class D:virtual public A
{
};
class E:virtual public A,virtual public B
{
};
class F
{
public:
int a;
static int b;
};
int F::b=10;
sizeof(A)=1
sizeof(B)=1
sizeof(C)=1
sizeof(D)=4
sizeof(E)=8
sizeof(F)=4
A是空类,编译器会安插一个char给空类,用来标记它的每一个对象。因此其大小为1
C是多重继承自A和B,其大小仍然为1字节
类D是是虚继承自A,编译器为该类安插一个指向父类的指针,指针大小为4.。由于此类有了指针,编译器不会安插一个char了。因此其大小是4个字节
类E虚继承自A并且也虚继承自B,因此它有指向父类A的指针与父类B的指针,加起来大小为8字节。
类F含有一个静态成员变量,这个静态成员的空间不在类的实例中,而是像全局变量一样在静态存储区中,被每一个类的实例共享,因此大小为4字节。
5.sizeof与strlen的区别
区别如下:
1.sizeof是操作符,strlen是函数
2.sizeof操作符的结果类型是size_t,它在头文件typedef为unsigned类型,该类型保证能容纳实现所建立的最大对象的字节大小。
3.sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以‘\0’结尾的。
4.数组做sizeof的参数不会退化,传递给strlen就退化为指针了。
5.大部分编译程序在编译的时候sizeof就被计算过了,这就是sizeof(x)可以用来定义数组维数的原因。strlen的结果要在运行的时候才能计算出来,它用来计算字符串的长度,不是类型占内存的大小。
6.sizeof后如果是类型,必须加括弧;如果是变量名,可以不加括弧。这是因为sizeof是个操作符,而不是个函数。
7.如果要计算指针指向的字符串的长度,则一定要用strlen。例如:
char *ss="0123456789";
int a=sizeof(ss);
int b=strlen(ss);
a计算的是ss指针占用的内存空间大小,b计算的是ss指向的字符串的长度
8.在计算字符串数组的长度上有区别。例如:
char str[20]="0123456789";
int a=strlen(str);
int b=sizeof(str);
a.计算的是以0x00结束的字符串的长度(不包括0x00结束符),这里结果是10
b.计算的则是分配的数组str[20]所占的内存空间的大小,不受里面存储内容的改变而改变,这里结果是20
6.sizeof有哪些用途
1.与存储分配和I/O系统那样的例程进行通信。例如:
void* malloc(size_t size);
size_t fread(void* ptr,size_t size,size_t nmemb,FILE* stream);
2.查看某个类型的对象在内存所占的单元字节。例如:
void* memset(void*s,int c,sizeof(s));
4.便于一些类型的扩充,在Windows中很多结构类型就有一个专用的字段是用来放该类型的字节大小的。
5.由于操作数的字节数在实现时可能出现变化,建议在涉及操作数字节大小时用sizeof来代替常量计算。
6.如果操作数是函数中的数组参数或函数类型的形参,则sizeof给出其指针的大小。
7.使用sizeof计算联合体的大小
联合体的大小取决于它所以的成员中占用空间最大的一个成员的大小。并且对于复合数据类型,如union、struct、class的对齐方式为成员中最大的成员对齐方式。
8.#pragma pack的作用
对齐是可以更改的,使用#pragma pack(x)可以改变编译器的对齐方式。
#include <iostream>
#prama pack(1)
struct test{
char c;
short s1;
short s2;
int i;
};
sizeof(test)=9;
如果注释第二行,则编译器默认对齐为8.sizeof(test)=12;
9.为什么要引入内联函数
引入内联函数的主要目的是,用它替代c语言中表达式形式的宏定义来解决程序中函数调用的效率问题。在c语言里可以使用如下的宏定义。
#define ExpressionName(Var1,Var2)(Var1+Var2)*(Var1-Var2)
这种宏定义在形式及使用上像一个函数,但它使用预处理器实现,没有了参数压栈、代码生成等一系列操作,因此效率很高。这种宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受c++编译器严格类型检查的好处。另外,它的返回值也不能被强制转换为可转换的合适类型,这样,它的使用就存在着一系列的隐患和局限性。
另外,在c++中引入了类及类的访问控制,这样,如果一个操作或者说一个表达式涉及类的保护成员或私有成员,你就不可能使用这样宏定义来实现(因为无法将this指针放在合适位置)。
inline推出的目的,也正是为了取代这种表达式形式的宏定义。它消除了它的缺点,同时又很好地继承了它的优点。
10.为什么inline能很好地取代表达式形式的预定义
1inline定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换(像宏一样展开),没有了调用的开销,效率也很高。
2.类的内联函数也是一个真正的函数。编译器在调用一个内联函数时,首先会检查它的参数的类型,保证调用正确;然后进行一系列的相关检查,就行对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
3.inline可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。
11.内联函数与宏有什么区别
内联函数在编译时展开,宏在预编译时展开。
在编译的时候,内联函数可以直接被镶嵌到目标代码中,而宏只是一个简单的文本替换。
内联函数可以完成诸如类型检测、语句是否正确等编译功能,宏就不具有这样的功能。
宏不是函数,inline函数是函数。
宏定义时要小心处理宏参数(一般情况是把参数用括号括起来),否则容易出现二义性。而内联函数定义时不会出现二义性。