[关闭]
@dantangfan 2015-01-21T17:12:59.000000Z 字数 26052 阅读 2337

C++语言基础篇

  1. c++写出读取文件最后K行。
    思路:利用一个K大小的循环数组,读取一遍最后按照读取顺序打印就可以
  1. void printLast10Lines(char *fileName){
  2. const int k=10;
  3. ifstream file(filename);
  4. string L[k];
  5. int size = 0;
  6. while(file.good()){
  7. getline(file,L(size%k));
  8. size++;
  9. }
  10. int start = size>k ? (size%k) : 0;
  11. int count = min(k,size);
  12. for(int i=0;i<count;i++)
  13. cout<<L[(start+i)%k]<<endl;
  14. }
  1. 虚函数的工作原理是什么
    虚函数需要虚函数表才能实现。如果一个类有函数声明成虚函数,就会生成一个vtable,存放这个类的虚函数地址。此外,编译器还会在类里加入隐藏的vptr变量。若子类没有复写虚函数,该子类的虚函数表就会存放父类的函数地址。调用这个虚函数时,就会通过vtable解析函数的地址。C++的动态绑定就是通过vtable实现的。由此,将子类对象赋值给基类指针,vptr就会指向子类的vtable。这样就能确保继承关系最末端的子类的虚函数会被调用到。
    考虑如下代码:
  1. class Shape{
  2. public:
  3. int edeg_length;
  4. virtual int circumference(){
  5. cout << Circumference of Base Class\n”;
  6. return 0;
  7. }
  8. };
  9. class Triangle: public Shape{
  10. public:
  11. int circumference(){
  12. cout << Circumference of Triangle Class\n”;
  13. return 3*edeg_length;
  14. }
  15. };
  16. int main(int argc,char **argv){
  17. Shape *X = new Shape();
  18. X->cricumference();//Base Class;
  19. Shape *Y = new Triangle();
  20. Y->circumference();//Triangle Class
  21. }
  1. 深拷贝与浅拷贝的区别
    浅拷贝会将对象所有成员的值拷贝到另一个对象里。除了拷贝多有成员的值,深拷贝还会进一步拷贝所有指针对象。
    如下:
  1. Struct Test{
  2. char *ptr;
  3. };
  4. void shallow_Copy(Test &src,Test &dest){
  5. dest.ptr = src.ptr;
  6. }
  7. void deep_Copy(Test &src,Test &dest){
  8. dest.ptr = (char *)malloc(strlen(src.ptr)+1);
  9. strcpy(dest.ptr,src.ptr);
  10. }

实际开发中,大部分情况都应该使用深拷贝。

  1. 如果C++程序要调用已经被编译后的C函数,该怎么办?
    C++程序不能直接调用已编译后的C函数的,这是因为名称问题,举个例,一个函数叫做void foo(int x, int y),该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字用来支持函数重载和类型安全连接,名称就不一样,因此不能直接调用的。那要调用的话怎么办呢?
    C++提供了一个C连接交换指定符号 extern “C”来解决这个问题
    如:
  1. extern C
  2. {
  3. #include myfile.h
  4. void foo (int x, int y);
  5. }

这就告诉C++编译译器,函数foo 是个C连接,应该到库中找名字_foo而不是找_foo_int_int。C++编译器开发商已经对C标准库的头文件作了extern“C”处理,所以我们可以用#include 直接引用这些头文件。
C++语言支持函数重载,C语言不支持函数重载。C++提供了C连接交换指定符号extern “C”
解决名字匹配问题
首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数
exten "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的:
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo( int x, int y );
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。
_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
未加extern "C"声明时的连接方式
假设在C++中,模块A的头文件如下:

  1. // 模块A头文件 moduleA.h
  2. #ifndef MODULE_A_H
  3. #define MODULE_A_H
  4. int foo( int x, int y );
  5. #endif  

在模块B中引用该函数:

  1. // 模块B实现文件 moduleB.cpp
  2. i nclude "moduleA.h"
  3. foo(2,3);

加exten "C"声明后的编译和连接方式
加extern "C"声明后,模块A的头文件变为:

  1. // 模块A头文件 moduleA.h
  2. #ifndef MODULE_A_H
  3. #define MODULE_A_H
  4. extern "C" int foo( int x, int y );
  5. #endif

在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:
(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;
(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。
如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。
所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C++与C及其它语言的混合编程。  
明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧:
extern "C"的惯用法
(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

  1. extern "C"
  2. {
  3. i nclude "cExample.h"
  4. }

而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。
C++引用C函数例子工程中包含的三个文件的源代码如下:

  1. /* c语言头文件:cExample.h */
  2. #ifndef C_EXAMPLE_H
  3. #define C_EXAMPLE_H
  4. extern int add(int x,int y);
  5. #endif
  6. /* c语言实现文件:cExample.c */
  7. i nclude "cExample.h"
  8. int add( int x, int y )
  9. {
  10. return x + y;
  11. }
  12. // c++实现文件,调用add:cppFile.cpp
  13. extern "C"
  14. {
  15. i nclude "cExample.h"
  16. }
  17. int main(int argc, char* argv[])
  18. {
  19. add(2,3);
  20. return 0;
  21. }

如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。
(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。
C引用C++函数例子工程中包含的三个文件的源代码如下:

  1. //C++头文件 cppExample.h
  2. #ifndef CPP_EXAMPLE_H
  3. #define CPP_EXAMPLE_H
  4. extern "C" int add( int x, int y );
  5. #endif
  6. //C++实现文件 cppExample.cpp
  7. i nclude "cppExample.h"
  8. int add( int x, int y )
  9. {
  10. return x + y;
  11. }
  12. /* C实现文件 cFile.c
  13. /* 这样会编译出错:#i nclude "cExample.h" */
  14. #include <stdio.h>
  15. #include "cppExample.h"
  16. extern int add(int x, int y);
  17. int main( int argc, char* argv[] )
  18. {
  19. printf("%d\n",add( 2, 3 ));
  20. return 0;
  21. }
  1. 重载中隐式转换产生的二意性
  1. # include <iostream.h>
  2. void output( int x); // 函数声明
  3. void output( float x); // 函数声明
  4. void output( int x)
  5. {
  6. cout << " output int " << x << endl ;
  7. }
  8. void output( float x)
  9. {
  10. cout << " output float " << x << endl ;
  11. }
  12. void main(void)
  13. {
  14. int x = 1;
  15. float y = 1.0;
  16. output(x); // output int 1
  17. output(y); // output float 1
  18. output(1); // output int 1
  19. // output(0.5); // error! ambiguous call, 因为自动类型转换
  20. output(int(0.5)); // output int 0
  21. output(float(0.5)); // output float 0.5
  22. }

第一个output函数的参数是int类型,第二个output函数的参数是float类型。由于数字本身没有类型,将数字当作参数时将自动进行类型转换(称为隐式类型转换)。语句output(0.5)将产生编译错误,因为编译器不知道该将0.5转换成int还是float类型的参数。

  1. 关于函数的重载、覆盖、隐藏
    成员函数被重载的特征:
    (1)相同的范围(在同一个类中);
    (2)函数名字相同;
    (3)参数不同;
    (4)virtual关键字可有可无。
    覆盖是指派生类函数覆盖基类函数,特征是:
    (1)不同的范围(分别位于派生类与基类);
    (2)函数名字相同;
    (3)参数相同;
    (4)基类函数必须有virtual关键字。
    前两者还很容易理解。但是函数的隐藏就不容易理解了。是这样说的:
    2)令人迷惑的隐藏规则
    本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性陡然增加。
    这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
    (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
    (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

  2. 函数的缺省值,只能出现在声明中,不能出现在函数定义体中
    void foo(int x, int y=0, int z=0);正确
    void foo(int x, int y=0, int z=0){}错误

  3. 内存对齐(主要在struct中)。
    如下

  1. struct test1{
  2. int a;
  3. int b;
  4. char c;
  5. }t1;
  6. sizeof(t1) = 12;
  7. struct test2{
  8. int a:2;
  9. int b;
  10. char c;
  11. }t2;
  12. sizeof(t2) = 12;
  13. struct test3{
  14. int a:2;
  15. char b;
  16. int c;
  17. }t3;
  18. sizeof(t3) = 8;
  1. 安全的赋值操作符号
  1. class test{
  2. public:
  3. test & opereate = (const test & rhs);
  4. private:
  5. string *str;
  6. }

常规情况下,我们总是实现一份不安全的赋值

  1. test & test::operator= (const test & rhs){
  2. delete str;
  3. str = new string(*rhs.str);
  4. return *this;
  5. }//当rhs==*this的时候就会出现错误

那么我们需要改进这个错误

  1. test & test::operator=(const test& rhs){
  2. if(this==&rhs) return *this;
  3. delete str;
  4. str = new string(*rhs.str);
  5. return *this;
  6. }//但是这样的情况还是存在问题,比如说new操作出错(虽然这样的情况很少发生),内存不足

那么我们就不能把原来的数据丢失了

  1. test &test::operator=(const test &rhs){
  2. if(this==&rhs) return *this;
  3. string *old = str;
  4. str = new string(*rhs.str);
  5. delete old;
  6. return *this;
  7. }

同上面,我们就可以使用一个叫做copy-and-swap的方法来简化代码.用一个副本来保存需要赋值的数据,然后与被赋值的交换。

  1. test & test::operator= (const test& rhs){
  2. test temp(rhs);
  3. swap(temp);
  4. return *this;
  5. }
  1. 避免遮掩继承而来的名称
    比如说下面类继承
  1. class Base{
  2. public:
  3. virtual void mf1() = 0;
  4. virtual void mf1(int);
  5. virtual void mf2();
  6. void mf3();
  7. void mf3(int);
  8. private:
  9. int x;
  10. };
  11. class derived:public Base{
  12. public:
  13. virtual void mf1();
  14. void mf3();
  15. void mf4();
  16. };

以作用域为基础的“名称遮掩规则”并没有改变,因此base class内所有名为mf1和mf3的 函数都被derived class内的mf1和mf3函数遮掩掉了。

  1. Derived d;
  2. int x;
  3. d.mf1();//ok,调用derived::mf1
  4. d.mf1(x);//wrong!, derived:mf1掩盖了Base::mf1;
  5. d.mf2();//ok,Base::mf2
  6. d.mf3();//ok,derived::mf3;
  7. d.mf3(x);//wrong!!,derived::mf3掩盖了Base::mf3

如我们看到的,规则都适用,即使参数不同也使用,不管是不是virtual都适用。但不幸的是我们经常需要重写哪些重名函数,这个时候有一个办法能及解决上面问题,使用using让base的函数变得可见

  1. class derived:public Base{
  2. public:
  3. using Base::mf1;//让Baee中名字叫的mf1和mf3的所有东西
  4. using Base::mf3;//在derived中变得可见并且是public
  5. virtual void mf1();
  6. void mf3();
  7. void mf4();
  8. };

那么现在调用就可以正常运行了,不过mf1和mf3都是属于derived类了

  1. Derived d;
  2. int x;
  3. d.mf1();//ok,调用derived::mf1
  4. d.mf1(x);//ok, derived:mf1
  5. d.mf2();//ok,Base::mf2
  6. d.mf3();//ok,derived::mf3;
  7. d.mf3(x);//ok,derived::mf3

注意:虽然我们这里说的很详细,但是我们应该遵循一个原则“绝不重新定义继承而来的non-virtual 函数”。因为在不适用vurtual的时候,不管是基类指针还是本身,他都只会调用本身类的函数。

  1. 虚基类与抽象类
    虚基类是相对于它的派生类而言的,它本身可以是一个普通的类。
    只有它的派生类虚继承它的时候,它才称作虚基类,如果没有虚继承的话,就称为基类。比如类B虚继承于类A,那类A就称作类B的虚基类,如果没有虚继承,那类B就只是类A的基类。
    虚继承主要用于一个类继承多个类的情况,避免重复继承同一个类两次或多次。
    例如 由类A派生类B和类C,类D又同时继承类B和类C,这时候类D就要用虚继承的方式避免重复继承类A两次。
    而抽象类是指带有有一个或一个以上的纯虚函数的类。抽象类一般值用于继承,不能定义类对象,但可以定义类指针和引用。

  2. 编写string.h中的某些函数
    注意!!!最大的问题就是判断是否是NULL,是否有重叠!!是否有返回值!!返回值的类型!!

  1. int strstr(char *str, char *substr){
  2. if ( str==NULL || substr==NULL )return -1;
  3. int lenstr = strlen(str);
  4. int lensub = strlen(substr);
  5. if( lenstr < lensub ) return -1;
  6. int len = lenstr lensub;
  7. int i ,j;
  8. for(i = 0;i<len;i++){
  9. for (j=0;j<lensub;j++)
  10. if(str[i+j]!=substr[j]) break;
  11. if(j==lensub)
  12. return i+1;
  13. }
  14. return -1;
  15. }
  16. char *strcpy(char *dest, char *src){
  17. if(dest==src) return dest;
  18. assert( (dest!=NULL) && (src!=NULL) );
  19. char *address = dest;
  20. while( (*dest++=*src++)!='\0' );
  21. return address;
  22. }
  23. int strcmp(const char *s, const char *t){
  24. assert(s!=NULL&&t!=NULL);
  25. while( *s && *t && *s==*t ){
  26. s++;
  27. t++;
  28. }
  29. return (*s-*t);
  30. }
  31. int strlen(const char *str){
  32. assert( s!=NUL );
  33. int len = 0;
  34. while(*s++!='\0') len++;
  35. return len;
  36. }
  37. char *strlwr(char *str){
  38. assert( str!=NULL );
  39. char *s = str;
  40. while(*s!='\0'){
  41. if(*s>'A'&&*s<'Z')
  42. *s+=0x20;
  43. s++;
  44. }
  45. return str;
  46. }
  47. void *memcpy(void *dest, void *src, unsigned int count){
  48. assert( dest!=NULL && src!=NULL );
  49. void *address = dest;
  50. while(count--){
  51. *(char *)dest = *(char *)src;
  52. dest = (char *)dest + 1;
  53. src = (char *)src +1;
  54. }
  55. return address;
  56. }
  57. void *memset(void *str, int c, unsigned int count){
  58. assert( str!=NULL );
  59. void *s = str;
  60. while(count--){
  61. *(char *)s = (char)c;
  62. s = (char *)s+1;
  63. }
  64. return str;
  65. }
  1. scanf和cin的问题
    A.scanf在读取字符发生错误的时候会自动退出但是不会清空缓冲区。如下错误
  1. int i=0;
  2. while(1){
  3. printf(“*”);
  4. scanf(“%d”,&i);
  5. if(i==1) break;
  6. }

当我们在上面输入任意一个不是%d的字符的时候,屏幕就会不断的输出****。这个时候我们需要做的就是手动去清除缓冲区(也就是把缓冲区里面的字符读取完)

  1. int i=0;
  2. char c;
  3. while(1){
  4. printf(“*”);
  5. scanf(“%d”,&i);
  6. while( (c=getchar())!='\n' && c!=EOF );
  7. if(i==1) break;
  8. }

B.scanf在读取字符串的时候,遇到空白字符就会自动停止当前的匹配。这样就会导致一个字符串读取不完整.
我们需要做的就是利用其他函数来代替scanf输入

  1. char buf[1024];
  2. fgets(buf, sizeof(buf),stdin);
  3. if(sscanf(buf,”%d %c %d”,&a,&b,&c)==3)
  4. printf(“OK\n”);

总结:
- scanf读取单个字符的时候任何字符都不放弃
- 读取数字和字符串的时候,空格等分隔符会被当成结束
- 读取成功会清空缓冲区,不成功不清空
- 键盘的输入都被保存到缓冲区中直到输入回车。输入函数会直接读取缓冲区,如果空则等待输入,不空不等待输入。

  1. 顺便回顾一下不那么常用的sscanf,sprintf,fscanf,fprintf,fgets,fputs

    • 首先记得,S打头的是字符串I/O,f打头的是文件I/O,scanf/printf为控制台输出.
    • sscanf() 从一个字符串中读进与指定格式相符的数据.sscanf(buf,”%d %d”,&a,&b);
    • sprintf() 输入到字符串.sprintf(buf, “%s %s”, a,b);我经常用它来作为数字转换为字符串
    • fscanf() 从流中按格式读取.fscanf(fp, “%d %d”, &a , &b);
    • fprinft() 按格式输入到流. fprintf(fp,”%d %d”,a,b);
    • fgets();从文件指针stream(流,可以是stdin)中读取n-1个字符,存到以s为起始地址的空间里,直到读完一行,如果成功则返回s的指针,否则返回NULL。 fgets(buf,sizeof(buf),stdin/FILE);
    • fputs();向指定的文件写入一个字符串(不自动写入字符串结束标记符‘\0’)。成功写入一个字符串后,文件的位置指针会自动后移,函数返回值为0;否则返回EOF(-1)。fputs(buf,sizeof(buf),stdout/FILE)
  2. 虚函数浅析。

  1. #include <iostream>
  2. using namespace std;
  3. class A{
  4. public:virtual void p()
  5. {
  6. cout << "A" << endl;
  7. }
  8. };
  9. class B : public A
  10. {
  11. public:virtual void p()
  12. { cout << "B" << endl;
  13. }
  14. };
  15. int main()
  16. {
  17. A * a = new A;
  18. A * b = new B;
  19. a->p();
  20. b->p();
  21. delete a;
  22. delete b;
  23. return 0;
  24. }

答案将输出AB
但是我们把virtual关键字去掉之后

  1. class A
  2. {
  3. public:
  4. void p()
  5. {
  6. cout << "A" << endl;
  7. }
  8. };
  9. class B : public A
  10. {
  11. public:
  12. void p()
  13. {
  14. cout << "B" << endl;
  15. }
  16. };

答案就成了AA
在构造一个类的对象时,如果它有基类,那么首先将构造基类的对象,然后 才构造派生类自己的对象
原因是A *b = new B(B *b = new B就会调用B的)构造了派生类B的对象,但是由于B是A的派生类,所以要先构造A对象然后再构造B对象。但是由于当程序中的函数是非虚函数的时候,B的函数p()的调用在编译时已经静态确定了,所以基类指针不管是指向哪里,都将调用基类函数,只要调用的函数不是虚函数就直接无视。

  1. inline

    • 内联函数编译时嵌入相应的位置。如果成员函数在类内直接定义,不需要加inline关键字,编译器直接优化为内联函数。内联函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原型是不够的。
    • 内联函数一般与宏比较,宏采用的是直接带入的方式,不进行类型检查。
    • 把内联函数的定义放在头文件中,可以确保在调用函数时所使用的定义是相同的,并且保证在调用点该函数的定义对编译器是可见的。
    • 如果内联函数在cpp中实现,则该函数只能在该cpp中使用。尽量不要放在cpp中实现。
  2. C++四种强制类型转化
    c语言中直接type b = (type)a;
    C++中可以使用这种方式:type b = 强制类型转换符(a)

    • reinterpret_cast:主要用于转换不相兼容的数据类型,特别是能够把任意数据类型转换为指针和引用类型,以及相反过程,也可以用于指针之间的转换。但是错误的使用reinterpret_cast会很不安全,所以很少使用。例:void *p; unsigned int val = reinterpret_cast(p)
    • static_cast:类型于C风格转换,是无条件转换和静态转换。用于:
      a. 基类和子类之间相互转换:子类指针转换成父类指针是安全的,反之不安全
      b. 基本数据类型转换
      c. 空指针转换成其他类型指针
      d. 任何类型的表达式转换成void
      e. 不能去掉类型的const和volitale属性。
      例子:
  1. double d = static_cast<double>(n);
  2. void *p = static_cast<void *>(ptr);
  3. class A{};
  4. class B:public A{};
  5. A *a = new A; B *b; b = static_cast<B *>(a);//不能子类转父类
- dynamic_cast:static_cast没有dynamic_cast安全,因为static不做类型检查,对于一个模糊指针用static_cast不会报错,但是用dynamic就会报错。dynamic_cast仅仅对指针和引用有效。

- const_cast:去掉类型的const或volalitle属性。不能在不同的类型间转换

例子:
int a=3;const int *b=&a; int *c = const_cast(b); *c=4;这个时候a变成了4。显然这样做是没有什么意义的,比如我可以直接int *c = &a;同样可以修改a的值。所以,我觉得这个关键字的用法应该这样*const_cast(b) = 5;

  1. 简单解释多态
    将父对象设置成和一个或多个他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给他的子对象的特性以不同的方式运作。简单说:允许将子类类型的指针赋值给父类类型的指针。多台可以简单的概括成“一种接口 多种实现”,在程序运行的时候才决定调用的函数,也就是动态绑定。
    多态的作用主要有两个:1隐藏实现的细节,使得代码能模块化,实现代码重用;2接口重用,为了类在继承和派生的时候,保证使用家族中任意类的某一属性时的正确调用。

  2. 简单说说const,volatile,mutable的用法

    • const
      const修饰普通变量和指针(cosnt)type(const)(*)(const) var;
      const 修饰函数参数,表示在函数体内不能修改
      const 修饰类对象/对象指针/对象引用,表示该对象为常量,里面的任何成员都不能被修改。const修饰的对象,其中的非const方法都不能调用,因为任何非const函数都有可能改变成员变量
  1. class AAA{
  2. public:
  3. void func1(){};
  4. void func2() const{};
  5. };
  6. const AAA obj;
  7. obj.func1();//错误
  8. obj.func2();//正确

const修饰的数据成员,值在某个对象的生存空间是常量。不同实例的const成员值可以不一样,所以不能呢个在类声明中初始化const

  1. class AAA{
  2. const int size=100;//错误
  3. };

它只能在初始化列表中初始化

  1. class AAA{
  2. public:
  3. AAA(int n):size(n){}
  4. private:
  5. const int size;
  6. };

也可以使用enum代替

  1. class AAA{
  2. private:
  3. enum{size=100};
  4. }
  5. const 修饰成员函数,一般把const放在函数的最后,表示不能修改该对象的成员变量
  6. class AAA{
  7. public:
  8. int haha()const{};
  9. }

const 修饰函数的返回值。除了重载操作符号,其他一般不常用。
- volatile:易变的
volatile的本意是“易变的”,volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
Mutabled:修饰的变量,就算是函数声明为const,也可以修改mutable修饰的变量。

  1. 内存管理
    20.1 内存的分配方式有三种:
    (1)静态存储区分配,内存在编译时已经分配好,在整个程序运行周期都存在,比如全局变量和static
    (2)在栈上创建,局部变量,在函数执行结束时自动释放,效率高,但容量有限
    (3)堆上分配——动态分配。生存周期由程序员决定,最容易出错
    20.2 常见内存错误
    • 内存未分配成功,却使用了。
      在使用内存之前检查指针是否是NULL。如果p是函数参数,则在函数入口使用assert(p!=NULL),如果是new/malloc分配的就if(p!=NULL)
    • 内存分配成功,但没有初始化就直接使用
      有时候内存会自动初始化成0(从经验看来,一般是第一次使用该内存块的时候),在内存释放之后,内存中的内容亦然没有被销毁,也就是说free/delete之后,内存并没有还给操作系统。那么在下次分配的内存如果不初始化直接使用会导致结果不确定。所以通常我们把malloc和meset配合使用
    • 内存分配成功,并且也初始化。但是超过了边界
    • 没有释放内存,导致内存泄漏
      new/delete,malloc/free一定要配对使用
    • 释放了内存,却又使用了它
      最容易出错的地方是程序返回值return的时候。如果不是malloc/new出来的内存,我们返回的都是栈内存,这样是错误的;还有一种就是释放内存delete/free之后,我们应当及时把指针指向NULL,避免产生野指针
      20.3 指针和数组
      需要注意的是,指针和数组只有在参数传递的时候才是一样的(数组自动转化为指针),其他地方都不一定
      另外还有sizeof的时候,指针通常返回的是一个int 的大小,而数组返回的是数组内部大小
      20.4 指针参数如何传递内存
      一种常见的错误:
  1. void get(char *str, int size){
  2. str = (char *)malloc(sizeof(char)*size);
  3. }

这里的str不会编程size大小的数组。因为编译器总是要为函数的每个参数制造临时副本,指针str的副本是_str,编译器使_str=str。如果函数体内的代码修改了_str,就会导致str的内容做相应的修改。这就是指针可以用作输出参数的原因。再上面代码中_str申请了新的内存,只是把_str的内存地址改变了,但是str自己却还没有改变(指针参数只能改变传入指针所指向的内容,而不是地址),所以get()函数不会输出任何东西。事实上,每执行一次函数,就会泄漏一次内存。
上面代码必须改成如下,才能正常执行

  1. void get(char **str,int size){
  2. *p = (char *)malloc(sizeof(char)*size);
  3. }
  4. main(){
  5. char *str;
  6. get(&str,10);//这里必须传入str的地址
  7. }

这很麻烦,也很费解。所以我们通常用更简单的办法来代替这个函数

  1. char *get(int size){
  2. char *str = (char *)malloc(sizeof(char)*size);
  3. return str;
  4. }

20.5 free和delete
需要知道的是,指针p被free掉之后内存地址并不会改变(不是NULL),只是该地址对应的内容变成了垃圾,p就成了传说中的野指针。如果这个时候不把p=NULL,if(p!=NULL)是不能判断正确的
20.6 防止野指针
野指针不是指向NULL,而是指向垃圾。野指针出现一般有两种可能
(1)使用没有初始化的指针。任何指针刚刚被创建的时候都不是NULL
(2)释放之后没有指向NULL
20.7 如何应对内存耗尽
有时候malloc和new找不到足够大的内存的时候,就会分配失败,返回NULL。比如我们在类中定义一个赋值操作的时候就应该防止内存分配不成功导致=操作符失败

  1. test &test::operator=(const test &p){
  2. test temp=new test(p);
  3. swap(temp);
  4. return *this;
  5. }
  1. class和struct的区别
    最本质的一个区别就是默认的访问控制:
    你可以写如下的代码:
  1. struct A
  2. {
  3. char a;
  4. };
  5. struct B : A
  6. {
  7. char b;
  8. };

这个时候B是public继承A的。
如果都将上面的struct改成class,那么B是private继承A的。这就是默认的继承访问权限。
(还有一个区别就是有时候template)

  1. 程序输出
  1. #include <stdio.h>
  2. int main() {
  3. char p[] = "\\\bSRE\0abc\\\b\n";
  4. printf("%d %d\n", strlen(p),sizeof(p));
  5. printf("%%s = %s", p);
  6. return 0;
  7. }

输出strlen(p) = 5,sizeof(p) = 13,p=”SRE”

  1. printf返回的是输出的字符的个数,而scanf返回的是匹配的字符的个数
  1. printf(“%d”,printf(“%d”,printf(“%d”,12)));//输出1221;
  1. 逗号表达式
  1. int a = 1,2;//错误
  2. int a = (1,2);//正确, a=2
  1. sizeof关键字的常见考点
  1. #include<iostream>
  2. using namespace std;
  3. struct A
  4. {};//1
  5. struct B
  6. {
  7. char c;
  8. int i;
  9. };//8
  10. struct C
  11. {
  12. int i;
  13. char c;
  14. };//8
  15. struct D
  16. {
  17. char c1;
  18. char c2;
  19. int i;
  20. };//8
  21. struct E
  22. {
  23. int i;
  24. char c1;
  25. char c2;
  26. };//8
  27. struct F
  28. {
  29. char c1;
  30. int i;
  31. char c2;
  32. };//12
  33. union G
  34. {
  35. char c1;
  36. int i;
  37. char c2;
  38. double d;
  39. };//8
  40. class H
  41. {};//1
  42. class I
  43. {
  44. private:
  45. int i;
  46. };//4
  47. class J
  48. {
  49. public:
  50. virtual void display();
  51. };//4
  52. class K
  53. {
  54. private:
  55. int i;
  56. public:
  57. void display();
  58. };//4
  59. class L
  60. {
  61. private:
  62. int i;
  63. public:
  64. virtual void display();
  65. };//8
  66. class M
  67. {
  68. private:
  69. int i;
  70. int j;
  71. public:
  72. virtual void display();
  73. };//12
  74. class N
  75. {
  76. private:
  77. static int i;
  78. };//1
  79. class O
  80. {
  81. private:
  82. static int i;
  83. int j;
  84. };//4
  85. void fun1(char str[20])
  86. {
  87. cout << sizeof(str) << endl;
  88. }
  89. void fun2(char a[10][9])
  90. {
  91. cout << sizeof(a) << endl;
  92. }
  93. int main()
  94. {
  95. char *p1;
  96. cout << sizeof(p1) << endl; // 4
  97. int *p2 = new int[100];
  98. cout << sizeof(p2) << endl; // 4
  99. delete [] p2;
  100. float *p3;
  101. cout << sizeof(p3) << endl; // 4
  102. double ******p4;
  103. cout << sizeof(p4) << endl; // 4
  104. char str[100] = "abcdefg";
  105. fun1(str); // 4 (fun1中的形参str是指针变量)
  106. cout << sizeof(str) << endl; // 100 (str的容量为100)
  107. cout << sizeof(*str) << endl; // 1 (*str是一个字符数据)
  108. cout << strlen(str) << endl; // 7 (字符串str的长度)
  109. char str1[] = "abcdeofg"; //(里面是字符'o')
  110. cout << sizeof(str1) << endl; // 9
  111. cout << strlen(str1) << endl; // 8
  112. char str2[] = "abcde0fg"; //(里面是字符'0',不等于'\0')
  113. cout << sizeof(str2) << endl; // 9
  114. cout << strlen(str2) << endl; // 8
  115. char str3[] = "abcde\0fg";
  116. cout << sizeof(str3) << endl; // 9
  117. cout << strlen(str3) << endl; // 5
  118. char str4[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
  119. cout << sizeof(str4) << endl; // 7
  120. cout << strlen(str4) << endl; // 13 (数值不确定)
  121. char str5[] = {'a', 'b', 'c', 'd', 'e', 'o', 'f', 'g'};
  122. cout << sizeof(str5) << endl; // 8
  123. cout << strlen(str5) << endl; // 21 (数值不确定)
  124. char str6[] = {'a', 'b', 'c', 'd', 'e', '0', 'f', 'g'};
  125. cout << sizeof(str6) << endl; // 8
  126. cout << strlen(str6) << endl; // 29 (数值不确定)
  127. char str7[] = {'a', 'b', 'c', 'd', 'e', '\0', 'f', 'g'};
  128. cout << sizeof(str7) << endl; // 8
  129. cout << strlen(str7) << endl; // 5
  130. char str8[] = {'a', 'b', 'c', 'd', 'e', 0, 'f', 'g'};
  131. cout << sizeof(str8) << endl; // 8
  132. cout << strlen(str8) << endl; // 5
  133. char str9[] = "";
  134. cout << sizeof(str9) << endl; // 1
  135. cout << strlen(str9) << endl; // 0
  136. char a[10][9];
  137. cout << sizeof(a) << endl; // 90
  138. fun2(a); // 4 (fun2中的a为指针变量)
  139. cout << sizeof(A) << endl; // 1 (编译器实现)
  140. cout << sizeof(B) << endl; // 8 (内存对齐)
  141. cout << sizeof(C) << endl; // 8 (内存对齐)
  142. cout << sizeof(D) << endl; // 8 (内存对齐)
  143. cout << sizeof(E) << endl; // 8 (内存对齐)
  144. cout << sizeof(F) << endl; // 12 (内存对齐)
  145. cout << sizeof(G) << endl; // 8 (内存共用)
  146. cout << sizeof(H) << endl; // 1 (编译器实现)
  147. cout << sizeof(I) << endl; // 4
  148. cout << sizeof(J) << endl; // 4 (虚指针)
  149. cout << sizeof(K) << endl; // 4
  150. cout << sizeof(L) << endl; // 8 (虚指针)
  151. cout << sizeof(M) << endl; // 12 (虚指针)
  152. cout << sizeof(N) << endl; // 1 (静态成员变量不专属某一对象)
  153. cout << sizeof(O) << endl; // 4 (静态成员变量不专属某一对象)
  154. return 0;
  155. }

总结:
Sizeof的作用非常简单:求对象或者类型的大小。然而sizeof又非常复杂,它涉及到很多特殊情况,本篇把这些情况分门别类,总结出了sizeof的10个特性:
(0)sizeof是运算符,不是函数;
(1)sizeof不能求得void类型的长度;
(2)sizeof能求得void类型的指针的长度;
(3)sizeof能求得静态分配内存的数组的长度!
(4)sizeof不能求得动态分配的内存的大小!
(5)sizeof不能对不完整的数组求长度;
(6)当表达式作为sizeof的操作数时,它返回表达式的计算结果的类型大小,但是它不对表达式求值!
(7)sizeof可以对函数调用求大小,并且求得的大小等于返回类型的大小,但是不执行函数体!
(8)sizeof求得的结构体(及其对象)的大小并不等于各个数据成员对象的大小之和!
(9)sizeof不能用于求结构体的位域成员的大小,但是可以求得包含位域成员的结构体的大小!

  1. Effective c++笔记
    自己看书,略

  2. 下面的程序并不见得会输出 hello-std-out,你知道为什么吗?

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. int main()
  4. {
  5. while(1)
  6. {
  7. fprintf(stdout,"hello-std-out");
  8. fprintf(stderr,"hello-std-err");
  9. sleep(1);
  10. }
  11. return 0;
  12. }

参考答案:stdout和stderr是不是同设备描述符。stdout是块设备,stderr则不是。对于块设备,只有当下面几种情况下才会被输入,1)遇到回车,2)缓冲区满,3)flush被调用。而stderr则不会。

  1. 下面的程序看起来是正常的,使用了一个逗号表达式来做初始化。可惜这段程序是有问题的。你知道为什么呢?
  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 1,2;
  5. printf("a : %d\n",a);
  6. return 0;
  7. }

参考答案:这个程序会得到编译出错(语法出错),逗号表达式是没错,可是在初始化和变量声明时,逗号并不是逗号表达式的意义。这点要区分,要修改上面这个程序,你需要加上括号: int a = (1,2);

  1. 下面的程序会有什么样的输出呢?
  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i=43;
  5. printf("%d\n",printf("%d",printf("%d",i)));
  6. return 0;
  7. }

参考答案:程序会输出4321,你知道为什么吗?要知道为什么,你需要知道printf的返回值是什么。printf返回值是输出的字符个数。

  1. 下面的程序会输出什么?
  1. #include <stdio.h>
  2. int main()
  3. {
  4. float a = 12.5;
  5. printf("%d\n", a);
  6. printf("%d\n", (int)a);
  7. printf("%d\n", *(int *)&a);
  8. return 0;
  9. }

参考答案:
该项程序输出如下所示,
0
12
1095237632
原因是:浮点数是4个字节,12.5f 转成二进制是:01000001010010000000000000000000,十六进制是:0×41480000,十进制是:1095237632。所以,第二和第三个输出相信大家也知道是为什么了。而对于第一个,为什么会输出0,我们需要了解一下float和double的内存布局,如下:
float: 1位符号位(s)、8位指数(e),23位尾数(m,共32位)
double: 1位符号位(s)、11位指数(e),52位尾数(m,共64位)
然后,我们还需要了解一下printf由于类型不匹配,所以,会把float直接转成double,注意,12.5的float和double的内存二进制完全不一样。别忘了在x86芯片下使用是的反字节序,高位字节和低位字位要反过来。所以
float版:0×41480000 (在内存中是:00 00 48 41)
double版:0×4029000000000000 (在内存中是:00 00 00 00 00 00 29 40)
而我们的%d要求是一个4字节的int,对于double的内存布局,我们可以看到前四个字节是00,所以输出自然是0了。
这个示例向我们说明printf并不是类型安全的,这就是为什么C++要引如cout的原因了。

  1. 下面,我们再来看一个交叉编译的事情,下面的两个文件可以编译通过吗?如果可以通过,结果是什么?
  1. file1.c
  2. int arr[80];
  3. ////////////////////
  4. file2.c
  5. extern int *arr;
  6. int main()
  7. {
  8. arr[1] = 100;
  9. printf("%d\n", arr[1]);
  10. return 0;
  11. }

参考答案:该程序可以编译通过,但运行时会出错。为什么呢?原因是,在另一个文件中用 extern int *arr来外部声明一个数组并不能得到实际的期望值,因为他们的类型并不匹配。所以导致指针实际并没有指向那个数组。注意:一个指向数组的指针,并不等于一个数组。修改:extern int arr[]。(参考:ISO C语言 6.5.4.2 节)
32. 请说出下面的程序输出是多少?并解释为什么?(注意,该程序并不会输出 “b is 20″)

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a=1;
  5. switch(a)
  6. {
  7. int b=20;
  8. case 1:
  9. printf("b is %d\n",b);
  10. break;
  11. default:
  12. printf("b is %d\n",b);
  13. break;
  14. }
  15. return 0;
  16. }

参考答案:该程序在编译时,可能会出现一条warning: unreachable code at beginning of switch statement。我们以为进入switch后,变量b会被初始化,其实并不然,因为switch-case语句会把变量b的初始化直接就跳过了。所以,程序会输出一个随机的内存值。

  1. 请问下面的程序会有什么潜在的危险?
  1. #include <stdio.h>
  2. int main()
  3. {
  4. char str[80];
  5. printf("Enter the string:");
  6. scanf("%s",str);
  7. printf("You entered:%s\n",str);
  8. return 0;
  9. }

参考答案:本题很简单了。这个程序的潜在问题是,如果用户输入了超过80个长度的字符,那么就会有数组越界的问题了,你的程序很有可以及会crash了。

  1. 请问下面的程序输出什么?
  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i;
  5. i = 10;
  6. printf("i : %d\n",i);
  7. printf("sizeof(i++) is: %d\n",sizeof(i++));
  8. printf("i : %d\n",i);
  9. return 0;
  10. }

参考答案:如果你觉得输出分别是,10,4,11,那么你就错了,错在了第三个,第一个是10没有什么问题,第二个是4,也没有什么问题,因为是32位机上一个int有4个字节。但是第三个为什么输出的不是11呢?居然还是10?原因是,sizeof不是一个函数,是一个操作符,其求i++的类型的size,这是一件可以在程序运行前(编译时)完全的事情,所以,sizeof(i++)直接就被4给取代了,在运行时也就不会有了i++这个表达式。

  1. 请问下面的程序的输出值是什么?
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define SIZEOF(arr) (sizeof(arr)/sizeof(arr[0]))
  4. #define PrintInt(expr) printf("%s:%d\n",#expr,(expr))
  5. int main()
  6. {
  7. /* The powers of 10 */
  8. int pot[] = {
  9. 0001,
  10. 0010,
  11. 0100,
  12. 1000
  13. };
  14. int i;
  15. for(i=0;i<SIZEOF(pot);i++)
  16. PrintInt(pot[i]);
  17. return 0;
  18. }

参考答案:好吧,如果你对于PrintInt这个宏有问题的话,你可以去看一看《语言的歧义》中的第四个示例。不过,本例的问题不在这里,本例的输出会是:1,8,64,1000,其实很简单了,以C/C++中,以0开头的数字都是八进制的。

  1. 请问下面的程序输出是什么?(绝对不是10)
  1. #include
  2. #define PrintInt(expr) printf("%s : %dn",#expr,(expr))
  3. int main()
  4. {
  5. int y = 100;
  6. int *p;
  7. p = malloc(sizeof(int));
  8. *p = 10;
  9. y = y/*p; /*dividing y by *p */;
  10. PrintInt(y);
  11. return 0;
  12. }

参考答案:本题输出的是100。为什么呢?问题就出在 y = y/*p;上了,我们本来想的是 y / (*p) ,然而,我们没有加入空格和括号,结果y/p中的 /被解释成了注释的开始。于是,这也是整个恶梦的开始。
37. 下面的输出是什么?

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i = 6;
  5. if( ((++i < 7) && ( i++/6)) || (++i <= 9))
  6. ;
  7. printf("%d\n",i);
  8. return 0;
  9. }

参考答案:本题并不简单的是考前缀++或反缀++,本题主要考的是&&和||的短路求值的问题。所为短路求值:对于(条件1 && 条件2),如果“条件1”是false,那“条件2”的表达式会被忽略了。对于(条件1 || 条件2),如果“条件1”为true,而“条件2”的表达式则被忽略了。所以,我相信你会知道本题的答案是什么了。

  1. 下面的C程序是合法的吗?如果是,那么输出是什么?
  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a=3, b = 5;
  5. printf(&a["Ya!Hello! how is this? %s\n"], &b["junk/super"]);
  6. printf(&a["WHAT%c%c%c %c%c %c !\n"], 1["this"],
  7. 2["beauty"],0["tool"],0["is"],3["sensitive"],4["CCCCCC"]);
  8. return 0;
  9. }

参考答案:
本例是合法的,输出如下:

  1. Hello! how is this? super
  2. That is C !

本例主要展示了一种另类的用法。下面的两种用法是相同的:

  1. hello”[2]
  2. 2["hello"]

如果你知道:a[i] 其实就是 *(a+i)也就是 *(i+a),所以如果写成 i[a] 应该也不难理解了。

  1. 请问下面的程序输出什么?(假设:输入 Hello, World)
  1. #include <stdio.h>
  2. int main()
  3. {
  4. char dummy[80];
  5. printf("Enter a string:\n");
  6. scanf("%[^r]",dummy);
  7. printf("%s\n",dummy);
  8. return 0;
  9. }

参考答案:本例的输出是“Hello, Wo”,scanf中的”%[^r]“是从中作梗的东西。意思是遇到字符r就结束了。

  1. 下面的程序试图使用“位操作”来完成“乘5”的操作,不过这个程序中有个BUG,你知道是什么吗?
  1. #include <stdio.h>
  2. #define PrintInt(expr) printf("%s : %d\n",#expr,(expr))
  3. int FiveTimes(int a)
  4. {
  5. int t;
  6. t = a<<2 + a;
  7. return t;
  8. }
  9. int main()
  10. {
  11. int a = 1, b = 2,c = 3;
  12. PrintInt(FiveTimes(a));
  13. PrintInt(FiveTimes(b));
  14. PrintInt(FiveTimes(c));
  15. return 0;
  16. }

参考答案:本题的问题在于函数FiveTimes中的表达式“t = a<<2 + a;”,对于a<<2这个位操作,优先级要比加法要低,所以这个表达式就成了“t = a << (2+a)”,于是我们就得不到我们想要的值。该程序修正如下:

  1. int FiveTimes(int a)
  2. {
  3. int t;
  4. t = (a<<2) + a;
  5. return t;
  6. }
  1. 交换两个数字,不适用临时变量
  1. a=a^b;b=a^b;a=a^b;
  2. a=a+b;b=a-b;a=a-b;
  3. 还可以用memcpy()
  1. 下面代码有什么问题
  1. class A{
  2. private:
  3. int value;
  4. public:
  5. A(int n){value=n;}
  6. A(A another){value = another.value;}
  7. }

代码中的拷贝构造函数有问题A(A another),参数的形参只能是本类类型的引用,标准的应该是const A another

  1. 构造函数,静态函数和内联函数可不可以是虚函数。

    • 构造函数和静态函数不可以,内联函数可以。
    • 构造函数不可以:虚函数的调用是通过一个虚指针来实现的,而这个虚指针是在构造过程中设定的
    • 静态函数不可以:静态函数的调用是不需要实例的,而虚函数需要从一个实例中获取虚指针,进而获取函数的地址,从而实现动态绑定
    • 内联函数可以:内联是在编译阶段用代码替换调用,而虚函数是在运行时动态绑定。虽然可以,但是一般不会这样做,代价太大了。
  2. 虚构函数必须是虚的吗?
    不是,如果是基类就必须是。虚函数形式的析构函数做要是为了在实现多态的时不造成内存泄漏(调用顺序)比如下面程序B的析构函数不能被调用,但是A写成虚后就可以调用了。

  1. #include <iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6. A() {cout<<"A Constructor\n";}
  7. ~A() {cout<<"A Destructor\n";}
  8. };
  9. class B:public A
  10. {
  11. public:
  12. B() { cout<<"B Constructor\n";}
  13. ~B() { cout<<"B Destructor\n";}
  14. };
  15. int main()
  16. {
  17. A *pa=new B();
  18. delete pa;
  19. return 0;
  20. }
  1. 定义一个不能被继承的类。
    容易想到,只要把构造和析构函数写成private就不能继承,在编译的时候就会报错。但是不能被继承的对象如何产生呢?可以使用静态函数。
  1. class FinalClass
  2. {
  3. public:
  4. static FinalClass* GetInstance()
  5. {
  6. return new FinalClass;
  7. }
  8. static void DeleteInstance(FinalClass* pInstance)
  9. {
  10. delete pInstance;
  11. pInstance = NULL;
  12. }
  13. private:
  14. FinalClass() {} //私有的构造函数
  15. ~FinalClass() {} //私有的析构函数
  16. };
  1. 实现一个只能实例化一次的类
    方法一:类似上题,构造析构函数私有,定义一个静态的类对象指针,通过静态函数初始化
  1. //Single.h定义
  2. #pragma once
  3. class Singleton
  4. {
  5. public:
  6. static Singleton* GetInstance();
  7. private:
  8. Singleton();
  9. ~Singleton();
  10. static Singleton *singleton;
  11. };
  12. //Singleton.cpp定义
  13. Singleton* Singleton::singleton = NULL; //静态成员初始化
  14. Singleton::Singleton()
  15. {
  16. }
  17. Singleton::~Singleton()
  18. {
  19. }
  20. Singleton* Singleton::GetInstance()
  21. {
  22. if(singleton == NULL)
  23. singleton = new Singleton();
  24. return singleton;
  25. }

方法二:利用局部静态变量只能初始化一次

  1. class Singleton
  2. {
  3. private:
  4. Singleton(){}
  5. public:
  6. static Singleton* GetInstance()
  7. {
  8. static Singleton singleton; //局部静态变量
  9. return &singleton;
  10. }
  11. };

20.问题20:static和const关键字尽可能多的作用。
- static关键字至少有下列作用:
(1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
- const关键字至少有下列作用:
(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为"左值"。

  1. 数组与指针的区别。
    解答:摘自《C专家编程》。
    (1)数组:保存数据;指针:保存数据的地址。
    (2)数组:直接访问数据;指针:间接访问数据,首先取得指针的内容,把它作为地址,然后从地址提取数据
    (3)数组:通常用于存储固定数目前数据类型相同的元素;指针:通常用于动态数据结构
    (4)数组:隐式分配和删除;指针:相关的函数为malloc(),free()。
    (5)数组:自身即为数据名;指针,通常指向匿名数据。

  2. 静态多态和动态多态

    • 静态多态(编译时绑定):由重载和模板实现
    • 动态多态(运行时绑定/动态绑定):由虚函数实现
  3. 程序输出

  1. union{
  2. int a;
  3. char b[3];
  4. }u;
  5. memset(&u,0,sizeof(u));
  6. u.b[0]=1;

u.a的输出是多少。
首先union内存是按照最大的那个存放于是sizeof(u)=4;然后内存中有四个位置分别存放二个16进制位,这里的b[0]刚好放在第三个上,内存显示为0x00,0x00,0x01,0x00,按照小段规则,a为65536
24. int/float/bool/指针 和0比较大小

  1. int:if(0==a)
  2. float:const float eps = 0.000001; if(a>=-eps&&a<=eps)
  3. bool:if(a)
  4. pointer:if(NULL==a)

c++11的几个常用特性

  1. 类成员函数之后的override和final。
    在编写多层次继承的时候可以极大的避免犯错,当然,你非要写成vritual int foo() final =0也是可以的。
    当面试官问什么样的类不能初始化,除了回答private dctor,也可以使用这个
  1. class Base{
  2. virtual void foo() = 0;
  3. virtual void bar() = 0
  4. }
  5. class Der1:public Base{
  6. virtual void foo() override{}
  7. virtual void bar() final {}
  8. virtual void baz() final {}//编译失败,override限定必须是重载的虚函数
  9. }
  10. class Der2:public Der1{
  11. virtual void foo() override{}
  12. virtual void bar() override{}//错误,已经final了,不能再继承了。
  13. }
  1. lambda表达式
    1)lambda表达式的引入标志,在[]里面可以是=或者&,来说明表达式‘捕获’数据以什么方式传递,=表示传值,&表示传引用(默认)
    2)lambda表达式的参数列表
    3)mutable标志(可省去),表示函数体可以修改传入的参数
    4)返回值
    5)函数体,也是需要进行的实际操作
    例1
  1. main(){
  2. int n=[](int x, int y){return x+y;}(4,5);
  3. }

可以看到,我们通过函数后面的()传递参数
例2

  1. main(){
  2. auto f=[](int x, int y){return x+y;};
  3. f(4,5);
  4. }

可以像函数调用一样使用lambda表达式,和普通函数一样。不过我觉得还是直接用普通函数的好
详细介绍
c++11中的lambda表达式规范如下
其中
(1)是完整的lambda表达式
(2)const类型的lambda表达式
(3)省略了返回值类型的lambda表达式,返回类型可以根据函数体中的return语句推演,如果没有return则是void类型的lambda
(4)省略了参数列表,表示没有参数,类似f()
mutable修饰符号说明lambda表达式体内的代码可以修改被捕获的变量,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
exception说明lambda表达式可否抛出异常,以及什么异常,类似void fun() throw(x,y)
attribute来声明属性
parameter指定参数
另外,catpure指定了在可见域范围内的lambda表达式码内可见的外部变量的列表
---[a,&b]a变量传值,b变量传引用
---[this]以值的方式捕获this指针,函数体内可以使用lambda所在类中的成员变量
---[=]函数体内可以使用任何lambda所在范围的的局部变量包括this指针,并且是传值的
---[&]函数体内可以使用任何lambda所在范围内的局部变量包括this指针,并且是传引用
---[=,&a,&b]除ab之外,其他都是传值
---[]不捕获外部的任何变量
综合实例

  1. vector<int> v;
  2. v.push_back(1);
  3. v.push_back(2);
  4. //无参数输出
  5. {
  6. for_each(v.begin(),v.end(),[](int t){cout<<t<<endl;});
  7. }
  8. //局部变量传值
  9. {
  10. int a=10;
  11. for_each(v.begin(),v.end(),[=](int t){cout<<t+a;});
  12. }
  13. //传引用,修改变量。输出11,13,12
  14. {
  15. int a=10;
  16. for_each(v.begin(),v.end(),[&])(int t)mutable{cout<<t+a;a++});
  17. cout<<a;
  18. }
  19. //传值,修改变量。输出11,13,10
  20. {
  21. int a=10;
  22. for_each(v.begin(),v.end(),[=])(int t)mutable{cout<<t+a;a++});
  23. cout<<a;
  24. }
  25. //传引用修改变量,可以不用mutable.输出11,13,10
  26. {
  27. int a=10;
  28. for_each(v.begin(),v.end(),[&a])(int t){cout<<t+a;a++});
  29. cout<<a;
  30. }
  31. //空表达式
  32. [](){}();
  33. []{}();
  1. auto关键字
    有两个作用自动类型推断和返回值占位
    类型推倒
  1. auto a=10;
  2. auto b='a';
  3. auto s(“hello”);
  4. auto it=vecto.begin();
  5. auto func = [](){cout<<”hello”<<endl;};
  6. //返回值占位
  7. template<typename T1,typename T2>
  8. auto compose(T1 t1,T2 t2) decltype(t1+t2){
  9. return t1+t2;
  10. }
  11. auto v = compose(1,1.2);//v是double类型

注意事项
可以使用volatile、const、&、*、&&来修饰auto
const auto a=5;
auto *pk = new int;
必须初始化,不能auto a;
auto不能和其他类型一起使用,比如int
模板不能声明成auto,比如template是错的

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注