C/C++刷题知识点总结

1、C++ const 常量折叠

所谓的常量折叠是编译器的一种优化技术,也就是代码编译时时 const 常量表达式直接替换成立即数。

不过需要注意的时,const 常量仍然会分配内存空间。

  1.  
    #include <iostream>
  2.  
    using namespace std;
  3.  
    int main(void)
  4.  
    {
  5.  
    const int a = 10;
  6.  
    int * p = (int *)(&a);
  7.  
    *p = 20;
  8.  
    cout<<"a = "<<a<<", *p = "<<*p<<endl;
  9.  
     
  10.  
    int& c = const_cast<int&>(a);
  11.  
    c = 20;
  12.  
    cout << a<<" "<< c<<endl;
  13.  
     return 0;
  14.  
    }

输出的结果为:a的值是10,*p的值和c的值都是20。


2、柔性数组

所谓的柔性数组,是指结构体的最后一个成员的为长度为0的数组,这个数组本身不占结构体的内存,只是一个地址标记,相当于一个指针,标记结构体的结束地址和数组的起始地址。结合malloc 在堆上动态分配内存,相当于在结构体的末尾分配了动态数组,此之谓柔性。

  1.  
    struct XXX{
  2.  
    int size;
  3.  
    void data[0];
  4.  
    }

malloc(sizeof(XXX) + buff_len) , 便相当于结构体的末尾分配了一个 data[buff_len] 的数组。


3、不对数组使用多态

对于函数 func(Base*p, int n),  但是当传入的函数的参数的类型是派生类的数组时,func() 函数中通过索引 p[i] 或者 *(p+i) 来访问对象时,一次跳过的内存字节数(步长)是 sizeof(Base) 大小,此时便会出现内存的混乱。

对于数组元素的访问和对对象的成员的访问都是通过转化为基地址加上偏移量的形式进行的,而偏移量在编译器就是确定的。

4、虚析构函数

如果基类的析构函数是非虚的,无论基类指针或引用指向的是基类对象还是派生类对象,也不论此时派生类对象的析构函数是否是虚函数,delete 基类指针只会调用基类的析构函数。

5、迭代器失效

序列容器的 .erase()成员函数

iterator erase (iterator position);
iterator erase (iterator first, iterator last);

注意返回值是一个迭代器,iter = container.erase(iter)  指向删除位置的下一个位置,所以如果此时再++运算,就会跳过一个元素。

关联容器的.erase()成员函数

(1)
     void erase (iterator position);
(2)
size_type erase (const key_type& k); //若key存在有删除 返回1   没有删除 返回0               
(3)
     void erase (iterator first, iterator last);

返回值是void,要想删除之后的迭代器指向删除位置的下一个位置,需要这样container.erase(iter++);  iter即就是指向了删除位置的下一个位置。

6、无符号和有符号的比较

  1.  
    int i = -1;
  2.  
    unsigned j = 1;
  3.  
    j < i; //表达式为真

如果表达式包含signed和unsigned int,signed会被转换为unsigned。-1 转化为unsigned是ffffffff.

  1.  
    char i = -1;
  2.  
    unsigned char j = 1;
  3.  
    j < i; //表达式为假

算术转换通常的是做整形提升(integral promotion),对于所有比int小的整形,包括char、signed char、unsigned char、short和unsigned short,如果该类型的所有可能的值都能包含在int内,它们就会被提升为int,否则被提升为unsigned int。如果将bool值提升为int,则false转换为0,true转换为1。

所以这里i 和 j 都会被提升为int 类型, 不过i仍然是int类型的 -1, j 为int类型的1; 仍然有 j > i.

做整形提升时,

unsigned char c = 0xe0;
char d = c;

前边的位置会补充符号为,所以这里c提升为int, 将是一个正整数,而d 提升为int, 将是一个负数。虽然d 和 c的二进制位是一样的。所以在作比较时,由于需要整形提升,所以将会是 c > d 为真。

7、构造函数和析构函数中调用虚函数

一般不要这么做,无论在基类还是派生类的构造函数和析构函数中调用虚函数,调用的都只会是基类的虚函数。原因,任何一个正在构建和正在析构的派生类对象只是一个基类对象。(见C++编程惯用法p77).

8、关键字和关键字出现的次数构成的pair来构造map可以模拟multiset

9、折半法查找

折半法查找判断循环结束的条件一定是low <= high, 一定要有=;最后才能从两个元素中锁定要high的那个,否则只能够锁定到low。


10、参数的入栈顺序

this指针不入栈,其他参数从右往左入栈,由于栈是向下增长的,所以可以通过左边的参数的地址推算右边参数的地址,这也是C语言中可变参数的原理。函数内部,按申明的顺序入栈,所以先定义的是大地址。


11、拷贝构造函数只能传引用,不能传值

传值,拷贝构造函数的调用时,实参到形参的传递就会调用拷贝构造函数,这样会陷入无休止的递归调用当中。

12、++a的值就是a的值,a++是个临时值(a本身的值再增加1).

参数的计算顺序从右往左,跟入栈的输入相同:  如果是临时变量,直接用临时变量的值代替临时变量;如果是左值,需要全部计算完,带入最终的值。

  1.  
    int b = 0;
  2.  
    printf("%d %d", b++, b++); //输出为 1, 0

先计算右边的b++的值,是个临时变量,为0,直接用0替换,然后b变成1;左边的b++也是个临时变量,值为1,直接用1替换,  所以最后的输出为 1,0

  1.  
    int b = 0;
  2.  
    printf("%d %d", ++b, ++b); //输出为2, 2

先计算右边++b, 值即为b的值,为1;然后计算左边表达式,表达式的值为b,最后相当于输出两次b 的值, 但是此时b的值为2. 所以最后的值为2, 2.

  1.  
    int b = 0;
  2.  
    printf("%d %d", b++, ++b); //输出为1 , 2 //先计算右边表达式的值,是b,计算完后,b的值是1;然后计算b++ ,是个临时变量,用1代替,b的值变为2
  1.  
    int b = 0;
  2.  
    printf("%d %d", ++b, b++); //输出为2, 0 //右边是个临时变量,用0代替,右边的值是b,b的值最后为2

13、C++冒号前边的是标签,包括访问控制public、private、protect , case(case后的一定要是某种整形类的数据), 还有自定义的标签 label,这是可以通过goto 语句跳转过去的

http://www.taobao.com

所以上边这句完全没问题,http: 是自定义的标签, // 后边的被注释掉了,也不会出错!

14、C++11允许按照对象的值类型来重载

  1.  
    struct Foo {
  2.  
    void foo() & { std::cout << "lvalue" << std::endl; }
  3.  
    void foo() && { std::cout << "rvalue" << std::endl; }
  4.  
    };

15、C风格的两个字符串中的空白会被忽略,合成为一个字符串。

char p[ ] = "hello"   "world";

16、枚举类型变量,实际是整形,需要占一个1个字节到sizeof(int) 的大小之间,和编译器的实现有关。

  1.  
    class A
  2.  
    {
  3.  
    int i;
  4.  
    union U
  5.  
    {
  6.  
    char buff[13];
  7.  
    int i;
  8.  
    }u;
  9.  
    void foo() { }
  10.  
    typedef char* (*f)(void*);
  11.  
    enum{red, green, blue} color;
  12.  
    }a;

sizeof(a) 的值为 24,最后的color 枚举类型变量需要一个 int 的大小。注意嵌套的类型, 类型本身不占内存, 是变量才占内存.


17、map的operator [] 运算, 如果关键字存在, 返回值是value 类型, 如果关键字不存在, 会将关键字插入, 返回返回插入的value 的引用, 对于类类型, 调用默认构造函数来初始化这个value, 对于内置类型, 初始化为 0. 所以对于const 类型的map, 不能进行 operator[] 运算,防止修改数据.

注意map的元素是pair 类型, 且pair<const key_type,mapped_type>也就是 .first 成员是个const 类型, 不能被修改.

18、C++中的overload,override、overwrite

overload是重载,在相同的作用域;override指的派生类和基类之间的,是覆盖,也就是虚函数(注意一定要函数签名一样,才能正确表现出多态),而且虚函数最好不要带默认参数,否则某人参数总是基类中的(C++编程规范第38条);overwrite是重写,指的是派生类和基类之间的相同函数名(除了多态,派生类和基类中使用相同的函数名(注意函数签名可以不同)是非常不好的设计,基类中的同名函数将被隐藏),这时派生类对象和指针将无法访问基类中的同名函数。另外用谁的指针将调用谁的函数。

19、this指针为空

一般的,类指针如果为空,可以调用类的成员函数,只要类的成员函数不访问非静态的成员变量,相当于给类的成员函数传入了一个空的this 指针,完全没有问题。


20、sizeof() 只会求表达式的类型,不会对表达式进行运算求值,而且sizeof() 是编译期求值。一般的赋值,是在运行时才进行的。所以这样

sizeof(((MyClass*)0)->m_i)

来求一个成员的大小完全没有问题。

21、assert 宏的使用

assert 宏是在运行时起作用,而且是条件为假时,终止程序,并给出定位到条件不满足的地方。一旦调试完,可以在 #include <assert.h> 语句之前,定义宏 #define NDEBUG来取消 assert 宏的作用。

原文地址:https://www.cnblogs.com/lidabo/p/14163027.html