1、定义一个空类,编译器会自动添加哪些函数?
class Empty
{
};
尽管没有定义任何函数,但我们可以通过以下方式使用这个类:
Empty e1;
Empty e2(e1);
e2 = e1;
因为当编译器发现你用上述方式使用这个类而却在类声明中没有定义一般构造函数(非复制构造函数)、复制构造函数、赋值操作符重载函数和析构函数时,会自动为其生成这些函数,上面的Empty类经编译后就对应着下面的类:
class Empty
{
public:
Empty(){…};
Empty(const Empty& rhs){…};
~Empty(){…};
Empty& operator=(const Empty& rhs){…};
};
编译器生成的默认构造函数和析构函数的主要任务是分别调用基类和非静态成员变量的构造和析构函数。生成的复制构造函数和赋值操作符重载函数只是单纯的将源对象的每一个非静态变量拷贝给目标对象。
2、单向链表遍历一遍找到倒数第n个元素。
用两个指针,是只要一遍的。nodea从head 开始遍历,nodeb从head 的第n个next 开始遍历。如果nodeb的next元素为null,这时nodea就是倒数第n个元素.
3、
第一:请问运行Test 函数会有什么样的结果?
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, “hello world”);
printf(str);
}
分析:程序崩溃。因为GetMemory 并不能传递动态内存,Test 函数中的 str 一直都是 NULL。strcpy(str, “hello world”);将使程序崩溃。
第二:请问运行Test 函数会有什么样的结果?
char *GetMemory(void)
{
char p[] = “hello world”;
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
分析:可能是乱码。因为GetMemory 返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原来的内容已经被清除,新内容不可知。
第三:请问运行Test 函数会有什么样的结果?
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, “hello”);
printf(str);
}
分析:
(1)能够输出hello
(2)内存泄漏
第四:请问运行Test 函数会有什么样的结果?
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
分析:
篡改动态内存区的内容,后果难以预料,非常危险。
因为free(str);之后,str 成为野指针,if(str != NULL)语句不起作用。
4、类的大小sizeof1空类
class A {};
1
大小为1。
类的实例化就是给每一个实例在内存中分配一块地址。空类被实例化时,会由编译器隐含的添加一个字节。所以空类的size为1。
2 虚函数
class A
{
public:
virtual void fun() {};
virtual void fun2() {};
};
大小为4。
当C++类中有虚函数的时候,会有一个指向虚函数表(V-table)的指针,所有的虚函数都在这个表中。指针大小为4,所以size为4。
在来看如下代码:
class A
{
public:
char b;
short c;
int a;
};
class B
{
public:
char a;
int c;
short b;
};
考虑数据对齐,大小分别为 8 和 12。如果我们将int 换成虚函数,回事什么结果呢?
class A
{
public:
char b;
short c;
virtual void fun() {}
};
class B
{
public:
char a;
virtual void fun() {}
short b;
};
大小分别为 8 8。 都是占4个字节,结果不一样。 这是因为,为了效率问题,编译器(gcc 和 微软)一般会把虚指针放在类的内存空间的最前面的位置,不管虚函数声明的位置。考虑对齐,大小都是 4 +1+1+2 = 8.
3 静态数据成员
class A
{
public:
char b;
virtual void fun() {};
static int c;
};
大小为8。
静态数据成员被编译器放在程序的一个global data members中,它是类的一个数据成员,但不影响类的大小。不管这个类产生了多少个实例,还是派生了多少新的类,静态数据成员只有一个实例。静态数据成员,一旦被声明,就已经存在。 考虑到数据对齐, 最终是8字节。
4 普通成员函数
class A
{
public:
void fun() {};
};
大小为1。
类的大小与构造函数,析构函数,普通成员函数无关。
5 普通单继承
class A
{
int c;
};
class B : public A
{
int a;
};
大小分别为4 和 8。 可以看到普通的继承就是基类的大小+派生类自身的大小。注意数据对齐。
注意:类的数据成员按其声明顺序加入内存,无访问权限无关,只看声明顺序。
class A
{
int a;
char b;
};
class C : public A
{
public:
char c;
};
上面这段代码,不同的编译器结果不同,VS的结果是 8 和 12, GCC是8 和 8。VS中 相当于
class C
{
A a;
char c;
};
A的大小为8,对齐值为4, 则考虑总体对齐 8 + 1 + 3(padding) = 12。
GCC 则是
class C
{
int a;
char b;
char c;
};
1
2
3
4
5
6
结果为 4 + 1 + 1 + 2 = 8。
6 含虚函数的单继承
class A
{
virtual void fun () {}
};
class C : public A
{
public:
virtual void fun2() {}
};
大小分别为4 和 4。派生类继承了基类的虚指针,所以大小为4。