剑指offer读书总结-->面试所需的基础知识

本篇博文主要总结剑指offer上第二章一些没有说的很清楚的问题。

常被问到的c++基础知识

问题一: 类型转换问题

c++ 中有哪 4 个与类型转换相关的关键字?各有什么特点?在哪些场合下使用?

  • reinterpret_cast

    • static_cast
    • dynamic_cast
    • const_cast
    • reinterpret_cast :转换一个指针为其它类型的指针;以及指针与足够大 的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。

      1. 对于指针类型转换:这个操作符能够在非相关 的类型之间转换。操作结果只是简单的从一个指针到别的指针的值的二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。

      2. 对于指针与整数转换:先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值。

      const_cast :用来去除 const 限定,对于 const 变量,我们不能修改它的值,但是有时候我们需要修改它值时该怎么办?这是就可以通过使用 const_cast 来修改 const 变量。

      const int constant = 21;
      const int* const_p = &constant;
      int* modifier = const_cast<int*>(const_p);
      *modifier = 7;

      static_cast :与 reinterpret_cast 类似,不仅可以用在指针和引用上,还可以用在基础数据和对象上,不同的是需要转换的两个类型具有一定的关系 static_cast 支持指向基类的指针和指向子类的指针之间的互相转换。但是从基类到子类的转换,用 static_cast 并不是安全的。

      dynamic_cast :是用来检查两者是否有继承关系。因此该运算符实际上只接受基于类对象的指针和引用的类转换

      问题二: sizeOf 问题

      定义一个空的类型,里面没有任何成员变量和成员函数,对该类型求 sizeOf ,得到的结果是多少?如果有虚函数结果又如何?看下面测试案例

      #include <iostream>
      using namespace std;
      class a {};
      class b {};
      class c :public a {//包含虚函数
          virtual void fun() = 0;
      };
      
      class d :public b, public c {};
      
      class e {
      private:
          int data;
      };
      class f {
      private:
          int data;
          static int data1;
      };
      int f::data1 = 0;
      
      class g {
      private:
          g(int a) {};
          ~g() {};
      };
      
      int main()
      {
          cout << "sizeof(a):空类=" << sizeof(a) << endl;
          cout << "sizeof(b):空类=" << sizeof(b) << endl;
          cout << "sizeof(c):包含虚函数=" << sizeof(c) << endl;
          cout << "sizeof(d):空类和包含虚函数的类=" << sizeof(d) << endl;
          cout << "sizeof(e):包含一个int数据成员=" << sizeof(d) << endl;
          cout << "sizeof(f):包含一个int数据成员和一个静态变量=" << sizeof(d) << endl;
          cout << "sizeof(g):包含构造函数和析构函数=" << sizeof(d) << endl;
          return  0;
      }

      程序结果:

      sizeof(a):空类=1
      sizeof(b):空类=1
      sizeof(c):包含虚函数=8
      sizeof(d):空类和包含虚函数的类=16
      sizeof(e):包含一个int数据成员=4
      sizeof(f):包含一个int数据成员和一个静态变量=4
      sizeof(g):包含构造函数和析构函数=1

      我们可以看到 sizeof(a),sizeof(b) 都为 1 ,空类型的实例不包含任何信息,本来求sizeOf 结果应该是 0 ,但是当我们声明该类型实例时,他必须在内存中占有一定的空间,否则无法使用这个实例。至于占多大空间,由编译器决定,VS中是1个字节。

      如果类中包含构造函数和虚函数和前面一样,还是1,调用构造函数和析构函数,只需要知道函数地址即可,与类型实例无关。

      如果类中包含虚函数,则为该类生成虚函数表,每一个实例都会添加指向虚函数表的指针。

      类d是由类b,c派生的,它的大小应该为二者之和5,为什么却是8呢?这是因为为了提高实例在内存中的存取效率.类的大小往往被调整到系统的整数倍.并采取就近的法则,里哪个最近的倍数,就是该类的大小,所以类d的大小为8个字节.

      类的静态数据成员 被编译器放在程序的一个 global data members 中,它是类的一个数据成员。但是它不影响类的大小,不管这个类实际产生了多少实例,还是派生了多少新的类,静态成员数据在类中永远只有一个实体存在,而类的非静态数据成员只有被实例化的时候,他们才存在。但是类的静态数据成员一旦被声明,无论类是否被实例化,它都已存在。可以这么说,类的静态数据成员是一种特殊的全局变量。

      总结:可以看出类的大小与它当中的构造函数,析构函数,以及其他的成员函数无关,只与它当中的成员数据,虚函数有关。

      从以上的几个例子不难发现类的大小:

      • 为类的非静态成员数据的类型大小之和。
      • 有编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针)。
      • 为了优化存取效率,进行的边缘调整。
      • 与类中的构造函数,析构函数以及其他的成员函数无关。

      问题三:构造函数问题

      class A{  
      private:  
          int value;  
      public:  
          A(int n){ value = n; }  
          A(A other){ value = other.value; }  
          void Print() {cout<<value<<endl; }  
      };  
      int main(void)  
      {  
          A a = 10;  
          A b = a;  
          b.Print();  
          return 0;  
      } 

      对上面这段代码进行分析编译运行的结果是:
      A、编译错误 B、编译成功,运行时程序崩溃 C、编译运行正常,输出10

      答案:A、编译错误。复制构造函数 A(A other) 传入的参数是 A 的一个实例。由于是传值参数,我们把形参复制到实参会调用复制构造函数。因此如果允许复制构造函数传值,就会在复制构造函数内调用复制构造函数,就会形成永无休止的递归调用从而导致栈溢出。

      上面加黑斜体的部分不是很理解,特意查了下调用复制构造函数的情况:

      #include <iostream>
      using namespace std;
      
      class A {
      public:
      
          A(int data) { cout << "类A复制构造函数被调用" << endl; };
          A(A& a) { cout << "类A的带对象参构造函数被调用" << endl; };
      
          void test(A a) {
      
          }
          A fun(A a)
          {
              return a;
          }
      };
      
      
      int main()
      {
          cout << "A a(int 1) "; A a(1); cout << endl;
          cout << "A b = a"; A b = a; cout << endl;
          cout << "A c(int 1)"; A c(1); cout << endl;
          cout << "c.test(A a)"; c.test(b); cout << endl;
          cout << "c.fun(A a)"; c.fun(a); cout << endl;
      }

      程序运行结果:

      A a(int 1) 类A复制构造函数被调用
      
      A b = aA的带对象参构造函数被调用
      
      A c(int 1)类A复制构造函数被调用
      
      c.test(A a)类A的带对象参构造函数被调用
      
      c.fun(A a)类A的带对象参构造函数被调用
      类A的带对象参构造函数被调用
      1. 显式或隐式地用同类型的一个对象来初始化另外一个对象;
      2. 作为实参传递给一个函数。
      3. 在函数体内返回一个对象时,也会调用返回值类型的拷贝构造函数。
      4. 初始化序列容器中的元素时。比如vector<string>svec(5)string 的缺省构造函数和拷贝构造函数都会被调用。

      5. 用列表的方式初始化数组元素时。 stringa[]=string(hello),string(world); 会调用 string 的拷贝构造函数。
      6. 由此我们可知:如果复制构造函数 A(A other) 传入的参数是 A 的一个实例a 时,会有 A other=a 时,就会调用构造函数。这样就会不断的递归调用复制构造函数,从而导致栈溢出。我们可以把构造函数修改为 A(constA&other) 也就是把传值参数改为常量引用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值