#include "stdafx.h"
#include<iostream>
using namespace std;
class A {
private:
int m_value;
public:
A(int value)
{
m_value = value;
}
void Print1() {
printf("hello world");
}
void Print2() {
printf("%d", m_value);
}
virtual void Print3() {
printf("hello world");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
A* pA;
pA->Print1();
pA->Print2();
pA->Print3();
return 0;
}
复制代码
答案是:Print1调用正常,打印出hello world,但运行至Print2时,程序崩溃。调用Print1时,并不须要pA的地址,由于Print1的函数地址是固定的。编译器会给Print1传入一个this指针,该指针为NULL,但在Print1中该this指针并无用到。只要程序运行时没有访问不应访问的内存就不会出错,所以运行正常。在运行print2时,须要this指针才能获得m_value的值。因为此时this指针为NULL,所以程序崩溃了。对于Print3这一虚函数,C++在调用虚函数的时候,要根据实例(即this指针指向的实例)中虚函数表指针获得虚函数表,再从虚函数表中找到函数的地址。因为这一步须要访问实例的地址(即this指针),而此时this指针为空指针,所以致使内存访问出错。ios
#include<iostream>
using namespace std;
class A {
public:
A()
{
Print();
}
virtual void Print() {
cout<<"A is constructed."<<endl;
}
};
class B: public A
{
public:
B()
{
Print();
}
virtual void Print() {
cout<<"B is constructed."<<endl;
}
};
int main() {
A* pA = new B();
delete pA;
return 0;
}
复制代码
前后打印出两行:A is constructed. B is constructed. 调用B的构造函数时,先会调用B的基类A的构造函数。而后在A的构造函数里调用Print。因为此时实例的类型B的部分尚未构造好,本质上它只是A的一个实例,他的虚函数表指针指向的是类型A的虚函数表。所以此时调用的Print是A::Print。接着调用类型B的构造函数,并调用Print。此时已经开始构造B,而且虚函数表的指针已指向类B的虚函数表地址,所以此时调用的Print是B::Print。数组
class A{
private:
int n1;
int n2;
public:
A(): n2(0), n1(n2 + 2)
{
}
void Print() {
std::cout << "n1: " << n1 << ", n2: " << n2 << std::endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
a.Print();
return 0;
}
复制代码
输出n1是一个随机的数字,n2为0。在C++中,成员变量的初始化顺序与变量在类型中的申明顺序相同,而与它们在构造函数的初始化列表中的顺序无关。 所以首先初始化n1,而初始n1的参数n2尚未初始化,是一个随机值。初始化n2时,根据参数0对其初始化,故n2=0。函数
class A{
private:
int value;
public:
A(int n)
{
value = n;
}
A(A other)
{
value = other.value;
}
void Print() {
std::cout << value << std::endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
A a = 10;
A b = a;
b.Print();
return 0;
}
复制代码
编译错误。在复制构造函数中传入的参数是A的一个实例。因为是传值,把形参拷贝到实参会调用复制构造函数。所以若是容许复制构造函数传值,那么会造成永无休止的递归并形成栈溢出。所以C++的标准不容许复制构造函数传值参数,而必须是传引用或者常量引用。 复制构造函数的参数须要改成:const A& other。ui
class A{
public:
virtual void Fun(int number = 10) {
std::cout << "A::Fun with number " << number;
}
};
class B:public A
{
public:
virtual void Fun(int number = 20) {
std::cout << "B::Fun with number " << number;
}
};
int main() {
B b;
A &a = b;
a.Fun();
return 0;
}
复制代码
输出 B::Fun with number 10。因为a是一个指向B实例的引用,所以在运行的时候会调用B::Fun。但缺省参数是在编译期决定的。在编译的时候,编译器只知道a是一个类型a的引用,具体指向什么类型在编译期是不能肯定的,所以会按照A::Fun的声明把缺省参数number设为10。这一题的关键在于理解肯定缺省参数的值是在编译的时候,但肯定引用、指针的虚函数调用哪一个类型的函数是在运行的时候。this
char* GetString1() {
char p[] = "Hello World";//指向临时分配的桟空间,当运行至函数体外时,空间将被释放
return p;
}
char* GetString2() {
char *p = "Hello World";//指向全局常量区
return p;
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("GetString1 returns: %s. \n", GetString1());
printf("GetString2 returns: %s. \n", GetString2());
return 0;
}
复制代码
输出两行,第一行GetString1 returns: 后面跟的是一串随机的内容,而第二行GetString2 returns: Hello World.两个函数的区别在于GetString1中是一个数组,而GetString2中是一个指针。运行到GetString1时,p是一个数组,会开辟一块内存,并拷贝"Hello World"初始化该数组。接着返回数组的首地址并退出该函数。因为p是GetString1内的一个局部变量,当运行到这个函数外面的时候,这个数组的内存会被释放掉。所以在_tmain函数里再去访问这个数组的内容时,结果是随机的。运行到GetString2时,p是一个指针,它指向的是字符串常量区的一个常量字符串。该常量字符串是一个全局的,并不会由于退出函数GetString2而被释放掉。spa
int _tmain(int argc, _TCHAR* argv[])
{
char str1[] = "hello world";//桟空间
char str2[] = "hello world";//桟空间,临时分配,地址不一样
char* str3 = "hello world";//常量区
char* str4 = "hello world";//指向同一块全局常量区
if(str1 == str2)
printf("str1 and str2 are same.\n");
else
printf("str1 and str2 are not same.\n");
if(str3 == str4)
printf("str3 and str4 are same.\n");
else
printf("str3 and str4 are not same.\n");
return 0;
}
复制代码
这个题目与上一题目相似。str1和str2是两个字符串数组。咱们会为它们分配两个长度为12个字节的空间,并把"hello world"的内容分别拷贝到数组中去。这是两个初始地址不一样的数组,所以比较str1和str2的值,会不相同。str3和str4是两个指针,咱们无需为它们分配内存以存储字符串的内容,而只须要把它们指向"hello world“在内存中的地址就能够了。因为"hello world”是常量字符串,它在内存中只有一个拷贝,所以str3和str4指向的是同一个地址。所以比较str3和str4的值,会是相同的。指针
void Test() {
class B {
public:
B(void)
{
cout<<"B\t";
}
~B(void)
{
cout<<"~B\t";
}
};
struct C {
C(void)
{
cout<<"C\t";
}
~C(void)
{
cout<<"~C\t";
}
};
struct D : B
{
D()
{
cout<<"D\t";
}
~D()
{
cout<<"~D\t";
}
private:
C c;
};
D d;
}
复制代码
运行结果:B C D ~D ~ C ~B。当实例化D对象时,因为继承自B,于是首先调用B的构造函数,以后初始化私有成员C,完成父类的构造与私有成员的初始化后再进入D的构造函数体内;以后,按照相反顺序完成对象的析构操做。初始化与赋值是不一样的,通常初始化是在初始化列表完成的,构造函数体中进行的是赋值操做。code
class A {
public:
int a;//4字节
char b;//1字节
double c;//8字节,以此为基本单位进行字节对齐,上面的两个变量对齐后共为8字节,加上当前字节数,共为8+8=16字节。
virtual void print()//虚函数,构建虚函数表,虚函数表指针须要4字节,字节对其,扩充为8字节 {
cout<<"this is father's function!"<<endl;
}
virtual void print1()//地址存于虚函数表 {
cout<<"this is father's function1!"<<endl;
}
virtual void print2()//无需分配内存 {
cout<<"this is father's function2!"<<endl;
}
private:
float d;//4字节,字节对其,扩充为8字节
};
class B : A//首先承载A的大小:32字节
{
public:
virtual void print()//修改虚函数表地址 {
cout<<"this is children's function!"<<endl;
}
void print1()//仅存有函数入口地址,无需分配内存 {
cout<<"this is children's function1!"<<endl;
}
private:
char e;//1字节,字节对齐,扩充为8字节(能够发现,继承后,字节对齐单位也放生变化)
};
int main(void) {
cout<<sizeof(A)<<" "<<sizeof(B)<<endl;
system("pause");
return 0;
}
复制代码
运行结果:32,40.这个题目解决的关键在于掌握字节对齐的相关知识点。具体见上面注释。对象
class A {
public:
virtual void foo() { }
};
class B {
public:
virtual void foo() { }
};
class C : public A , public B
{
public:
virtual void foo() { }
};
void bar1(A *pa) {
B *pc = dynamic_cast<B*>(pa);//运行期遍历继承树
}
void bar2(A *pa) {
B *pc = static_cast<B*>(pa);//两个类无关,编译出错
}
void bar3() {
C c;
A *pa = &c;
B *pb = static_cast<B*>(static_cast<C*>(pa));//存在继承关系,编译正确
}
复制代码
对于bar1,dynamic_cast是在运行时遍历继承树,因此,在编译时不会报错。可是由于A和B无继承关系,因此运行时报错。static_cast:编译器隐式执行的任何类型转换均可由它显示完成。其中对于:(1)基本类型。如能够将int转换为double(编译器会执行隐式转换),可是不能将int用它转换到double(没有此隐式转换)。(2)对于用户自定义类型,若是两个类无关,则会出错,若是存在继承关系,则能够在基类和派生类之间进行任何转型,在编译期间不会出错。因此bar3能够经过编译。继承
class A {
public:
string a;
void f1() {
printf("Hello World");
}
void f2() {
a = "Hello World";
printf("%s",a.c_str());
}
virtual void f3() {
a = "Hello World";
printf("%s",a.c_str());
}
static void f4() {
printf("Hello World");
}
};
int main(void) {
A *aptr = NULL; //建立一个A对象,对象指针为空,意味着对象仅有空壳,没法借助指针访问成员变量
aptr->f1(); //运行成功,调用f1函数仅需函数入口地址,无需访问对象中的成员变量
aptr->f2(); //运行失败,调用f2需访问成员变量
aptr->f3(); //运行失败,同上
aptr->f4(); //静态成员不属于任何对象,运行成功
return 0;
}
复制代码
此题解答如程序注释所示。
int func() {
char b[2]={0};
strcpy(b,"aaa");
}
复制代码
Debug版崩溃,Release版正常。由于在Debug中有ASSERT断言保护,因此要崩溃,而在Release中就会删掉ASSERT,因此正常运行。可是不推荐这么作,由于这样会覆盖不属于本身的内存。