2

这里记录一下在找工作前的复习过程中那些让我怀疑我是不是真的学过计算机的知识点。。


C/C++

cout的格式化输出

cout << boolalpha << (1 == 2);

输出false
参考链接:http://www.cnblogs.com/devymex/archive/2010/09/06/1818754.html

const与指针

const*的左侧,如 const int *aint const *a,不能改变指针a指向的内容的值
const*的右侧,如 int* const a,指针a本身为常量,不能改变a的指向

个人觉得把const放在类型符号后边意义更明确一些。。

mutable与const

在类中,修饰为mutable的成员变量可以被修饰为const的成员函数修改

class A {
 public:
  mutable int a;
  void set_a(int n) const {
    a = n;
  }
};

sizeof括号内的秘密

sizeof()结果由编译时确定,()内代码不被执行

int a = 0;
sizeof(a = 1);

a的值不会被改变

指针和引用的区别与联系

注意,引用也是要占用内存空间的,内容是被引用的变量的地址,同指针一样

struct st1
{
	int a;
	int &b;
	st1(int n) : a(n), b(a) {}
};

struct st2
{
	int a;
	int *b;
	st2(int n) : a(n), b(&a) {}
};

st1 s1(10);
st2 *ps2 = reinterpret_cast<st2*>(&s1);
cout << *(ps2->b); // 10

参考链接:http://blog.csdn.net/thisispan/article/details/7456169

指针数组,还是数组指针,还是函数指针数组,还是。。

int *a[10];//包含十个元素的数组,数组元素是整型指针
int *(a[10]);//同上
int (*a)[10];//指向具有十个整型元素的数组的指针
int (*a)();//指向无参数且返回整型的函数的指针
int (*a[10])();//包含十个元素的数组,数组元素是指向无参数且返回整型的函数的指针
int (**a)[10];//指向一维数组指针的指针,数组具有十个整型元素
int *(*a)[10];//指向一维数组的指针,数组具有十个整型指针元素
int (*(*a)(int))();//指向具有一个整型参数且返回函数指针的函数的指针,返回的函数指针指向无参数且返回整型的函数

数组名取址?

int a[5] = {1, 2, 3, 4, 5};
int *p = (int*)(&a + 1);
cout << (int)*(p - 1);

输出为5
数组名a是指向数组第一个元素的指针,&a是指向数组的指针,虽然两个指向同一个地址,但是&a + 1实际上是将指针向后移动了sizeof(a),也就是20个字节,与a + 1移动一个字节不同
在这里p类似于二维数组

this指针从哪来

this指针实际上是类成员函数的第一个参数,由编译器自动添加,通过寄存器传递,整个生命周期同函数参数一致

容器与迭代器

删除vector等连续空间的容器元素后,相应迭代器会失效

struct与class

structclass除了默认访问控制不同以外,其他完全相同。
struct默认访问控制是publicclass默认访问控制是private

构造函数初始化列表

除开父类构造函数,成员变量初始化按照成员变量在类中申明的顺序执行,与初始化列表顺序无关

类与内联函数

除了虚函数和静态成员函数,其他函数可以是内联的,包括构造函数和析构函数。
事实上,如果成员函数在类申明中定义,那么默认是内联的。类申明外定义的函数可以用inline修饰为内联的。但需要注意的是,内联函数是内部链接的,只能在本文件内被调用。

//A.h
class A {
 public:
  A() { cout << "A()" << endl; }
  inline virtual ~A() { cout << "~A()" << endl; }
  inline void func1() { cout << "func1" << endl; }
  void func2();
};

//A.cc
inline void A::func2() { cout << "func2" << endl; }

//main.cc
A *a = new A;
a->func1();
a->func2();// 编译失败
delete a;

看起来好像复制构造函数

class A {
 public:
  int m;
  A(int n) : m(n) {}
  A(float n) : m(n * 2) {}
};

A a = 10;  // a.m == 10
A a = 10.0;// a.m == 20

单个参数的函数如果不添加explicit,会发生隐式的类型转换

私有继承与保护继承

私有继承与保护继承的派生类不能称为子类,因为派生类并不能完成父类能做的所有事情

虚继承与多重继承

参考链接:http://www.oschina.net/translate/cpp-virtual-inheritance

类申明的几个技巧

如何让一个类不能被实例化?

将构造函数申明为private或纯虚类(抽象类)

如何让一个类只能在堆上实例化?

将析构函数申明为private

如何让一个类只能在栈上实例化?

重载new运算符为private

如何让一个类的对象不能被复制?

将复制构造函数和赋值函数声明为private,或者声明为 = delete

如何让一个类不能被继承?

加final关键字
其他方法:http://blog.chinaunix.net/uid-26983585-id-4373697.html

四种cast

static_cast

static_cast和C语言里的强制类型转换相似,不过编译时会做类型检查,只有相关的类型才能转换,否则编译错误

dynamic_cast

dynamic_cast在程序运行时根据对象的虚表进行向上或者向下转换。转换指针失败时,返回nullptr,转换引用失败时,抛出异常
然而Google并不建议使用dynamic_cast,参考:http://www.zhihu.com/question/22445339

const_cast

const_cast去除对象的只读属性

reinterpret_cast

reinterpret_cast不做二进制转换,仅仅是重新解释二进制块,常常用在偏底层的操作上,可移植性稍弱

成员函数与对象

class A {
 public:
  static int m;
  int n;
  void func1() { cout << this << ' ' << m << endl; }
  void func2() { cout << this << ' ' << n << endl; }
  static void func3() { cout << m << endl; }
};
int A::m = 10;

A *a = 0;
a->func1();// 0 10
a->func2();// crash
a->func3();// 10
A::func3();// 10

如果对象指针指向错误的地址,对象成员函数仍然可以被调用,但是不能访问非静态成员变量。这里同时也将静态成员函数拿出来比较。
想一想,假如成员函数是虚函数,结果还会一样么?

位域

struct A {
  int a:1;
  int b:2;
  int c:3;
};

cout << sizeof(A);// 4

参考链接:http://www.cnblogs.com/bigrabbit/archive/2012/09/20/2695543.html

static的三个域

  • 模块

  • 函数

起到的作用有什么异同

volatile

volatile关键字告诉编译器不要优化被它修饰的变量,这个变量可能在编译器控制范围外发生改变,比如中断或者其他线程
注意下面这个例子

int func(volatile int *p) {
  // return (*p) * (*p); // wrong
  int temp = *p;
  return temp * temp;
}

变量提升

不同类型的变量参与混合运算时,所有变量将会转换为同一类型,为了不丢失精度,总是转换为长度更长的变量
在32位机器上,char,short总是转换为int,float总是转换为double
一般情况下,不用关注转换的问题,但是考虑下面一个例子:

signed char a = 0xe0;
unsigned int b = a;
cout << int(a) << ' ' << b;

结果 -32 4294967264
因为在a赋值给b时,a要提升为32位的unsigned int型。然而a是负数,在提升长度时高位将用1来补全,于是a被提升为0xffffffe0。赋值后,b的值为0xffffffe0,所以作为无符号整型输出为4294967264

const成员变量初始化

const成员变量必须在构造函数初始化列表中被初始化。

class A {
 public:
  const int m;
  A(int n) : m(n) {}
};

static const成员变量可以在声明时初始化

class A {
 public:
  static const int m = 0;
  A(int n) {}
};

static成员变量不能在声明时初始化,也不能在构造函数初始化列表中初始化,只能在类外申明并初始化

C++ 11已经支持非静态(static const例外)成员变量声明时初始化,自定义类型不能执行带参数的构造函数

函数调用方式

C和C++程序中,函数的调用有多种方式,可以通过在函数声明时加上__stdcall__cdecl__fastcall来显式指定
不同的函数调用方式体现在参数传递方式,编译后函数命名方式,函数返回时栈清理方式的不同。
参考链接:http://www.cnblogs.com/zhangchaoyang/articles/2725189.html

变长参数

简单的说,具有变长参数的函数在调用时(__cdecl方式),编译器按正常的方式将参数从右到左入栈,函数内部再通过指针根据参数类型依次从栈中将参数取出

printf("%c,%d", 'A', 10);举栗
因为参数从右到左进栈且栈是从高地址到低地址生长的,所以此时内存是这样的:

低地址<<|    4 byte   | 1 byte | 4 byte |>>高地址
        |"%c,%d"的地址|  'A'   |   10   |

首先printf函数的第一个参数不是变长参数,这里我们假设这个参数叫str,它是指向"%c,%d"的指针。有了str,我们自然就知道了变长参数的起始地址是&str+1,然后再根据"%c,%d",我们知道了变长参数有两个而且类型分别为charint,于是我们就可以依次得到这两个变量辣
参考链接:http://www.cnblogs.com/chinazhangjie/archive/2012/08/18/2645475.html

改变const变量的值

通过const_cast可以去除read-only属性,但是编译时编译器仍将使用到const变量的地方直接替换为初始化的那个值,类似于宏

int const a = 1;
int *p = const_cast<int*>(&a);
*p = 2;
cout << a << " " << *p; // 输出: 1 2

如何重写基类虚函数

override,貌似又是C++ 11的关键字

class A {
 public:
  virtual void func(int n);
}

class B : A {
 public:
  virtual void func(int n); // 不要这样,如果参数写错了,相当于声明了重载函数
  void func(int n); // 同上
  void func(int n) override; // 强制重写基类函数,如果参数或返回值与基类虚函数不一致,编译器报错
}

未完......


spacelan
925 声望4 粉丝