2016-01-14
原文:使用sizeof计算虚拟继承的类对象的空间大小 出现频率:★★★★ 1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 }; 7 8 class B 9 { 10 }; 11 12 class C:public A, public B 13 { 14 }; 15
2016-01-14
原文:16 class D:virtual public A 17 { 18 }; 19 20 class E:virtual public A, virtual public B 21 { 22 }; 23
2016-01-14
原文:在计算字符串数组的长度上有区别。例如, 1 char str[20]="0123456789"; 2 int a=strlen(str); 3 int b=sizeof(str); a计算的是以0x00结束的字符串的长度(不包括0x00结束符),这里结果是10。 b计算的则是分配的数组str[20]所占的内存空间的大小,不受里面存储内容的改变而改变,这里结果是20。 如果要计算指针指向的字符串的长度,则一定要使用strlen。例如。 1 char* ss = "0123456789"; 2 int
a = sizeof(ss); 3 int b = strlen(ss); a计算的是ss指针占用的内存空间大小,这里结果是4。 b计算的是ss指向的字符串的长度,这里结果是10。
2016-02-25
原文:下列关于this指针的叙述中,正确的是( )。 A.任何与类相关的函数都有this指针 B.类的成员函数都有this指针 C.类的友元函数都有this指针 D.类的非静态成员函数才有this指针 【解析】 A 错误。类的非静态成员函数是属于类的对象,含有this指针。而类的static 函数属于类本身,不含this指针。 B 错误。类的非静态成员函数是属于类的对象,含有this指针。而类的static 函数属于类本身,不含this指针。 C 错误。友元函数是非成员函数,所以它无法通过this 指针获得一份拷贝。
D 正确。
2016-02-25
原文:这里需要明白类函数是如何被编译以及如何被执行的。 对于类成员函数而言,并不是一个对象对应一个单独的成员函数体,而是此类的所有对象共用这个成员函数体。当程序被编译之后,此成员函数地址即已确定。我们常说,调用类成员函数时,会将当前对象的 this 指针传给成员函数。没错,一个类的成员函数体只有一份,而成员函数之所以能把属于此类的各个对象的数据区别开,就在于每次执行类成员函数时,都会把当前对象的 this 指针(对象首地址)传入成员函数,函数体内所有对类数据成员的访问,都会被转化为this->数据成员的方式。
如果print函数里没有访问对象的任何数据成员,那么此时传进来的对象this指针实际上是没有任何用处的。这样的函数,其特征与全局函数并没有太大区别。但如果取消第 14行的注释,由于print函数要访问类的数据成员data,而类的数据成员是伴随着对象声明而产生的。但是,我们只 new 了一个 MyClass,显然,下标"1"和下标"10000000"的 MyClass对象根本不存在,那么对它们的数据成员访问也显然是非法的。
2016-03-31
原文:unsigned int zero = 0; unsigned int compzero = 0xFFFF; /*1‘s complement of zero */ 【解析】 对于一个int型不是16位的处理器来说,上面的代码是不正确的。应编写如下: unsigned int compzero = ~0; 这一问题能真正揭露出应试者是否懂得处理器字长的重要性。好的嵌入式程序员能够非常准确地明白硬件的细节和它的局限性,然而PC机程序员往往把硬件作为一个无法避
2016-04-06
原文:大家知道,析构函数是为了在对象不被使用之后释放它的资源,虚函数是为了实现多态。那么,把析构函数声明为virtual有什么作用呢?请看下面的代码。1 #include <iostream>2 using namespace std;34 class Base5 {6 public:7 Base() {}; //Base的构造函数8 ~Base() //Base的析构函数9 {10 cout << "Output from the destructor of class Base!"
<< endl;11 };12 virtual void DoSomething()13 {
2016-04-06
原文:14 cout << "Do something in class Base!" << endl;15 };16 };1718 class Derived : public Base19 {20 public:21 Derived() {}; //Derived的构造函数22 ~Derived() //Derived的析构函数23 {24 cout << "Output from the destructor of class Derived!" << endl;25 };26 void
DoSomething()27 {28 cout << "Do something in class Derived!" << endl;29 };30 };3132 int main()33 {34 Derived *pTest1 = new Derived(); //Derived类
2016-04-06
原文:的指针35 pTest1->DoSomething();36 delete pTest1;3738 cout << endl;3940 Base *pTest2 = new Derived(); //Base类的指针41 pTest2->DoSomething();42 delete pTest2;4344 return 0;45 }先看程序输出结果:1 Do something in class Derived!2 Output from the destructor of class
Derived!3 Output from the destructor of class Base!45 Do something in class Derived!6 Output from the destructor of class Base!代码第36行可以正常释放pTest1的资源,而代码第42行没有正常释放pTest2的资源,因
2016-04-06
原文:为从结果看,Derived类的析构函数并没有被调用。通常情况下,类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。原因是指针pTest2是Base类型的指针,释放pTest2时只进行Base类的析构函数。在代码第8行前面加上virtual关键字后的运行结果如下。1 Do something in class Derived!2 Output from the destructor of class Derived!3 Output from the destructor of class
Base!45 Do something in class Derived!6 Output from the destructor of class Derived!7 Output from the destructor of class Base!此时释放指针pTest2时,由于Base的析构函数是virtual的,就会先找到并执行Derived类的析构函数,然后执行Base类的析构函数,资源正常释放,避免了内存泄漏。因此,只有当一个类被用来作为基类的时候,才会把析构函数写成虚函数。
2016-04-07
原文:构造函数与赋值函数的区别 出现频率:★★★ 【解析】 有3个方面的区别: (1)复制构造是一个对象来初始化一块内存区域,这块内存就是新对象的内存区。例如 1 class A; 2 A a; 3 A b=a; //复制构造函数调用
2016-04-07
原文:5 A b(a); //复制构造函数调用 而赋值函数是对于一个已经被初始化的对象来进行 operator=操作。例如 1 class A; 2 A a; 3 A b; 4 b = a; //赋值函数调用 (2)一般来说是在数据成员包含指针对象的时候,应付两种不同的处理需求:一种是复制指针对象,一种是引用指针对象。复制构造函数在大多数情况下是复制,赋值函数则是引用对象。 (3)实现不一样。复制构造函数首先是一个构造函数,它调用的时候是通过参数传进来的那个对象来初始化产生一个对象。赋值函数则是把一个对象赋值给一个原有的对象,所以,如果原来的对象中有内存分配,要先把内存释放掉,而且还要检查一下两个对象是不是同一个对象,如果是的话,就不做任何操作。
2016-04-13
原文:1 #include <iostream>2 using namespace std;34 class Person5 {6 public:7 virtual void print() {cout << "I‘m a Person" << endl;}8 };9 class Chinese : public Person10 {11 public:12 virtual void print() {cout << "I‘m from China" << endl;}13 };1415 class American
: public Person16 {
2016-04-13
原文:28 Person p;29 Chinese c;30 American a;31 printPerson(p);32 printPerson(c);33 printPerson(a);34 return 0;35 }执行结果如下。1 I‘m a Person2 I‘m from China3 I‘m from USA可以看到,在运行时通过基类Person的对象,可以来调用派生类Chinese和American中的实现方法。Chinese和American的方法都是通过覆盖基类中的虚函数方法来实现的。
2016-04-14
原文:7 virtual void doSth() { printf("I am A"); }8 };910 class B : public A11 {12 public:13 virtual void doSth() { printf("I am B"); }14 } ;1516 int main()17 {18 B b;19 return 0;
2016-04-14
原文:调用哪一份拷贝,从而产生错误。对于这个问题,通常有两个解决方案: (1)加上全局符确定调用哪一份拷贝。比如pa.Author::eat()调用属于Author的拷贝。 (2)使用虚拟继承,使得多重继承类Programmer_Author只拥有Person类的一份拷贝。比如在第11行和第17行的继承语句中加入virtual就可以了。 1 class Author : virtual public Person //Author虚拟继承自Person 2 class Programmer : virtual
public Person //Programmer虚拟继承自Person 【答案】 实际生活中,一些事物往往会拥有两个或两个以上事物的属性,为了解决这个问题, C++引入了多重继承的概念。 多重继承的优点是对象可以调用多个基类中的接口。 多重继承的缺点是容易出现继承向上的二义性。
2016-04-14
原文:多重继承中二义性的消除 出现频率:★★★★ 类A派生B和C,类D从B,C派生,如何将一个类A的指针指向一个类D的实例? 【解析】 这道题实际上考查的是如何消除多重继承引起的向上继承二义性问题。程序代码如下所示。 1 class A {}; 2 class B : public A {}; 3 class C : public A {}; 4 class D : public B, public C {}; 5
2016-04-14
原文:6 int main() 7 { 8 D d; 9 A *pd = &d; //编译错误 10 return 0; 11 } 由于B、C继承自A,B、C都拥有A的一份拷贝,类D多重继承自B、C,因此拥有A的两份拷贝。如果此时一个类A的指针指向一个类D的实例,会出现“模糊的转换”之类的编译错误。解决办法如下。 1 class A {}; 2 class B : virtual public A {}; //B虚拟继承自A 3 class C : virtual public A {}; //C虚拟继承自A
4 class D : public B, public C {}; 5
2016-04-14
原文:6 int main() 7 { 8 D d; 9 A *pd = &d; //成功转换 10 return 0; 11 } 将B、C都改为虚拟继承自A,则类D多重继承自B、C时,就不会重复拥有A的拷贝了,因此也就不会出现转换错误了。 【答案】 把B、C都改为虚拟继承自A,消除继承的二义性。
2016-04-18
原文:COM(Component Object Mode)即组件对象模型,是组件之间相互接口的规范。其作用是使各种软件构件和应用软件能够用一种统一的标准方式进行交互。COM不是一种面向对象的语言,而是一种与源代码无关的二进制标准。ActiveX 是 Microsoft 提出的一套基于 COM 的构件技术标准,实际上是对象嵌入与链接(OLE)的新版本。基于分布式环境下的 COM 被称作 DCOM(Distribute COM,分布式组件对象模型),它实现了 COM 对象与远程计算机上的另一个对象之间直接进行交互。DCOM
规范定义了分散对象创建和对象间通信的机制,DCOM是ActiveX的基础,因为ActiveX主要是针对Internet应用开发(相比OLE)的技术,当然也可以用于普通的桌面应用程序。
2016-04-18
原文:一个类会有多少张虚函数表呢?对于一个单继承的类,如果它有虚拟函数,则只有一张虚函数表。对于多重继承的类,它可能有多张虚函数表。考虑下面代码中的各个类的定义。1 #include <iostream>2 using namespace std;34 class Base15 {
2016-05-04
原文:希尔(Shell)排序算法先将要排序的一组数按某个增量 d 分成若干组,每组中记录的下标相差 d 对每组中全部元素进行排序,然后用一个较小的增量对它进行再次分组,并对每个新组重新进行排序。当增量减到 1 时,整个要排序的数被分成一组,排序完成。因此希尔排序实质上是一种分组插入方法。希尔排序的时间性能优于直接插入排序,其原因如下。当数组初始状态基本有序时,直接插入排序所需的比较和移动次数均较少。当n 值较小时,n 和n2 的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。在希尔排序开始时,增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量
d 逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多。但由于已经按d-1作为距离排过序,数组较接近于有序状态,所以新的一趟排序过程也较快。因此,希尔排序在效率上较直接插入排序有较大的改进。
2016-05-04
原文:2.实现我们可以这样来设置增量:初始时取序列的一半为增量,以后每次减半,直到增量为1。代码如下。1 #include <iostream>2 using namespace std;34 void shell_sort(int a[], int len)5 {6 int h, i, j, temp;78 for (h=len/2; h>0;
2016-05-09
原文:.基数排序基数排序是箱排序的改进和推广。箱排序也称桶排序(Bucket Sort),其基本思想是:设置若干个箱子,依次扫描待排序的记录R[0],R[1],…,R[n-1],把关键字等于k的记录全都装入到第k个箱子里(分配),然后按序号依次将各非空的箱子首尾连接起来(收集)。例如,要将一副混洗的52张扑克牌按点数A<2<…<J<Q<K排序,需设置13个“箱子”,排序时依次将每张牌按点数放入相应的箱子里,然后依次将这些箱子首尾相接,就得到了按点数递增顺序排列的一副牌。基数排序是基于多关键字的,什么是多关键字呢?如果文件中任何一个记录
R[i]的关键字都由 d 个分量构成,而且这 d 个分量中每个分量都是一个独立的关键字,则文件是多关键字的(比如扑克牌有两个关键字:点数和花色)。通常实现多关键字排序有两种
2016-05-11
原文:模板有两种特例化:部分模板特例化和全部模板特例化。 全部模板特例化就是模板中的模板参数全被指定为确定的类型,也就是定义了一个全新的类型。全部模板特例化的类中的函数可以与模板类不一样,例如 1 template <class A, class B, class C> 2 class X {}; //(a) 3 template <>
2016-05-11
原文:4 class X<int, float, string> {}; //(b) 若编译器遇到X<int, float, string>的模板实例化请求,则使用特例化后的版本,即(b)。其他任何类型的组合都是用基本模板,即(a)。 部分模板特例化就是模板中的模板参数没有被全部确定,需要编译器在编译时进行确定。它通常有两种基本情况: (1)对部分模板参数进行特例化: 1 template < class B, class C> 2 class X <int, B, C> {...}; // (c) 当编译器遇到X
<int, float, string> 的模板实例化请求时,使用这个特例化的版本(c)。而当编译器遇到X <int, double, char> 时,也是用这个特例化版本。也就是说,只要X<>实例化时,第一个模板实参是int,就使用特例化版本。 (2)使用具有某一特征的类型,对模板参数进行特例化: 1 template <class T>
2016-05-11
原文:2 class Y {...}; //(d) 3 template <class T> 4 class Y<T*> {...}; //(e) 当编译器遇到Y<int*>时,使用特例化模板(e)。当编译器遇到T<float*>时,也使用特例化模板(e)。而其他类型的实例化,如Y<int>,则使用基本模板(d)。也就是说,当模板实参符合特例化版本所需的特征时(在上面例子中是某个类型的指针),则使用特例化版本。 这两种情况有时会混合使用,比如 1 template <class A, class B> 2 class
Z {...}; //(f) 3 template <typename A> 4 class Z <A&, char> {...}; //(g) 当编译器遇到Z<int&, char>或者Z<string&, char>时,使用特例化模板(g)。其他情况使用基本模板(f),比如Z<int&, float>或Z<int, char>等。
2016-05-15
原文:可以看到,为了对数组 a 以及容器 v 中的所有元素进行逆置,我们都只调用了一个reverse方法。这里有几点要注意:reverse 是个全局函数,而不是成员函数。reverse 有两个参数,第一个参数是指向要操作的范围的头的指针,第二个参数是指向尾的指针。reverse 操作一定范围的元素而不是仅仅操作容器,本题中对数组也进行了操作。reverse和其他STL算法一样,它们是通用的,也就是说,reverse不仅可以用来颠倒向量的元素,也可以用来颠倒链表中元素的顺序,甚至可以对数组操作。它们实际上都是函数模板。
2016-05-17
原文:4647 int y = 1;48 int x = 0;49 y = y / x; //产生异常,TestPtr指向对象的内存仍然能得到释放50 return 0;51 }在这里,我们定义了一个Test类用于测试。Test类有一个成员name,并实现它的构造函数、赋值函数以及析构函数,另外还有ShowName()打印name字符串。下面是使用auto_ptr操作的步骤。代码第37行,创建并初始化auto_ptr。auto_ptr后面的尖括
2016-05-17
原文:简单地说,函数对象就是一个重载了“()”运算符的类的对象,它可以像一个函数一样使用。例如1 #include <iostream>2 using namespace std;34 class MyAdd5 {6 public:7 int operator () (int a, int b) { return a+b; }8 };910 class MyMinus11 {12 public:13 int operator () (int a, int b) { return a-b; }14 };1516 int
main()17 {
2016-05-17
原文:18 int a = 1;19 int b = 2;20 MyAdd addObj;21 MyMinus minusObj;22 cout << "a+b=" << addObj(a, b) << endl; //输出 a+b=323 cout << "a-b=" << minusObj(a, b) << endl; //输出a-b=-124 return 0;25 }可以看出,由于类MyAdd和类MyMinus都重载了“()”运算符,因此它们的对象addObj和minusObj可以像函数那样使用。STL中提供了一元和二元函数的两种函数对象。下面列出了各个函数对象。一元函数对象:negate,相反数。二元函数对象:plus,加(+)法。minus,减(-)法。multiplies,乘(*)法。divides,除(/)法。modulus,求余(%)。equal_to,等于(==)。not_equal_to,不等于(!=)。