C++笔记整理

xiaoxiao2021-02-28  44

把自己印象笔记中所记录的一些C++知识点整合了一下,可用于面试前对C++知识的快速回顾。

不过并不全,只是自己笔记中的摘要,重要的还是系统和踏实地学习。

每个知识点不分顺序。

1.typeid是什么

typeid用于类的类型检查 检查是否是 同一类型 一般用于 指针类 D d; C *p=d; typeid(*p)==typeid(D)则满足 返回类型是type_info

2.define的一些注意点

#define a 10 void foo();  main(){     printf ( "%d.." ,a);     foo();     printf ( "%d" ,a); } void foo(){     #undef a     #define a 50    //只影响这句话之后的内容 } 答案输出10..10 另外,不管是在某个函数内,还是在函数外,define都是从 定义开始直到文件结尾 ,所以如果把foo函数放到main上面的话,则结果会是50 ,50 #define a 10 void foo(); void prin();   int main() {      prin();      printf ( "%d " , a);      foo();      printf ( "%d " , a);        } void foo() { #undef a #define a 50 } void prin() {      printf ( "%d " , a);   //此处的a已经被替换 } 上面代码输出 50 10 10,可以看出define只是在预处理阶段将a替换为相应数值,具体替换的值,只与define在文件中的位置有关,与是否在函数内无关。

3.重载,重定义,重写是什么?

1.重写(override):       父类与子类之间的多态性。子类重新定义父类中有相同名称和参数的虚函数 1)被重写的函数不能是static的。必须是virtual的(即函数在最原始的基类中被声明为virtual )。 2)重写函数必须有相同的类型,名称和参数列表(即相同的函数原型) 3)重写函数的访问修饰符(public...)可以不同。尽管virtual是private的,派生类中重写改写为public,protected也是可以的   2.重载(overload):       指函数名相同,但是它的参数表列个数或顺序,类型不同。但是不能靠返回类型来判断。   3.重定义(redefining):       子类重新定义父类中有相同名称的非虚函数(参数列表可以不同)。   重写与重载的区别 (override) PK (overload) 1、方法的重写是子类和父类之间的关系,是垂直关系;方法的重载是同一个类中方法之间的关系,是水平关系。 2、重写要求参数列表相同;重载要求参数列表不同。 3、重写关系中,调用那个方法体,是根据 对象的类型(对象对应存储空间类型)来决定;       重载关系,是根据 调用时的实参表与形参表来选择 方法体的。

4.C++中的空类,默认产生哪些类成员函数?

class Empty {   public:       Empty(); // 缺省构造函数       Empty( const Empty& ); // 拷贝构造函数       ~Empty(); // 析构函数        Empty& operator=( const Empty& ); //  赋值运算符        Empty* operator&(); //  取址运算符        const Empty* operator&() const; //  取址运算符 const };

5.运算符重载

规则: 不可重载"." 或"*",这2个是用来访问成员的 不可重载“::”,作用域分辨符的操作数是类型 不可重载“?”,是三目运算符 返回类型 operator 运算符 (形参) {      函数体 } 成员函数的运算符重载比非成员函数少一个形参 因为自身this就是一个 重载自加运算符后可以返回对象的引用, 以方便在表达式中连续使用。 举个例: cout<<其实是重载了<<这个操作符,cout是一个ostream的对象。如果不返回引用,cout<<a<<b<<endl; 若不是引用,则cout<<a就不是一个输出流对象,就无法继续接b在屏幕上输出了。  就不可以一起写了。如果返回自身的引用 cout<<a之后,返回身身的引用,后面可以继续接b了。就是这个意思吧。好多重载操作符,作用其实都是这样的。 #include <iostream> #include <fstream> #include <string> using namespace std; class z; ostream & operator<< (ostream &out,z z1); class z{ public: int real,imag; z(int r=0,int i=0):real(r),imag(i){}; z operator+ (const z &z1) const; z operator- ();//无参数,前置负号 z &operator++ () ; //前置,即++z z operator++ (int) ; //后置,即z++ friend ostream & operator<< (ostream &out,z z1); void show(); }; //友元函数,不用加:: ostream & operator<< (ostream &out,z z1) { out<<"("<<z1.real<<","<<z1.imag<<")"; return out; } z z::operator+ (const z &z1) const { return z(this->real+z1.real,imag+z1.imag); } z &z::operator++ () { real++; imag++; return *this; } z z::operator++ (int ) { return z(real++,imag++); } z z::operator- () { return z(-real,-imag); } void z::show() { printf("(%d,%d)\n",real,imag); } int main() { z a(1,5),b(3,1); cout<<"a+b="; (a+b).show(); (a++).show(); a.show(); (++b).show(); b.show(); (-b).show(); cout<<"a="<<a<<endl; }

6.模板类和类模板的区别

模板定义:模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。 Template <class或者也可以用typename T> 返回类型 函数名(形参表) {//函数定义体 } //声明一个函数模版,用来比较输入的两个相同数据类型的参数的大小,class也可以被typename代替,  //T可以被任何字母或者数字代替。  template <class T>  T min(T x,T y)  {      return(x<y)?x:y;  }   模板类和重载函数一起使用时:      两者一起使用时, 先考虑重载函数 后考虑模板类 ,如过再找不到,就考虑类型转换,可能会带来精度的变化。      类模板: template <class T>  class Base  {  public :          T a ;      Base(T b)  {          a = b ;          }         T getA(){ return a ;} //类内定义       void setA(T c);  };    template <class T>   //模板在类外的定义   void  Base<T>::setA(T c)  {      a = c ;  }  

7.static 和 const 的作用

static关键字至少有下列5个作用: (1) 函数体内变量 。函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值 (2) 模块内全局变量 。在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问; (3) 模块内函数 。在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内; (4) 类内成员变量 。在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝; (5) 类内成员函数 。在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。  const关键字至少有下列n个作用: (1)欲 阻止一个变量被改变 ,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了; (2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const; (3)在一个函数声明中, const可以修饰形参 ,表明它是一个输入参数,在函数内部不能改变其值; (4)对于类的成员函数,若指定其为const类型,则表明其是一个 常函数,不能修改类的 成员变量 (5)对于类的成员函数,有时候必须 指定其返回值为const类型 ,以使得其返回值不为“左值”。例如: const classA operator*(const classA& a1,const classA& a2); operator*的返回结果必须是一个const对象。如果不是,这样的变态代码也不会编译出错: classA a, b, c; (a * b) = c; // 对a*b的结果赋值 操作(a * b) = c显然不符合编程者的初衷,也没有任何意义。

8.String类的实现

编写类String的构造函数、析构函数和赋值函数,已知类String的原型为: 1 2 3 4 5 6 7 8 9 10 class   String   public   String( const   char   *str = NULL);  // 普通构造函数    String( const   String &other);  // 拷贝构造函数    ~ String( void );  // 析构函数    String & operator =( const   String &other);  // 赋值函数    private   char   *m_data;  // 用于保存字符串  };

参考答案

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 //普通构造函数 String::String( const   char   *str)  {   if (str==NULL)    {   m_data =  new   char [1];  // 得分点:对空字符串自动申请存放结束标志'\0'的空   //加分点:对m_data加NULL 判断   *m_data =  '\0'     else {   int   length =  strlen (str);    m_data =  new   char [length+1];  // 若能加 NULL 判断则更好    strcpy (m_data, str);    } } // String的析构函数 String::~String( void {   delete   [] m_data;  // 或delete m_data; } //拷贝构造函数 String::String( const   String &other)     // 得分点:输入参数为const型   int   length =  strlen (other.m_data);    m_data =  new   char [length+1];      //加分点:对m_data加NULL 判断   strcpy (m_data, other.m_data);  } //赋值函数 String & String::operate =( const   String &other)  // 得分点:输入参数为const型   if ( this   == &other)    //得分点:检查自赋值   return   * this   delete   [] m_data;      //得分点:释放原有的内存资源   int   length =  strlen ( other.m_data );    m_data =  new   char [length+1];   //加分点:对m_data加NULL 判断   strcpy ( m_data, other.m_data );    return   * this ;          //得分点:返回本对象的引用 }

9.assert的作用

参数:Expression (including pointers) that evaluates to nonzero or 0.(表达式【包括指针】是非零或零) 原理:assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。 用法总结: 1) 在函数开始处检验传入参数的合法性 如: int resetBufferSize(int nNewSize) {   //功能:改变缓冲区大小,   //参数:nNewSize 缓冲区新长度 //返回值:缓冲区当前长度 //说明:保持原信息内容不变     nNewSize<=0表示清除缓冲区 assert (nNewSize >= 0); assert (nNewSize <= MAX_BUFFER_SIZE);   ... } 2) 每个assert只检验一个条件 , 因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败 不好:  assert (nOffset>=0 && nOffset+nSize<=m_nInfomationSize); 好:  assert (nOffset >= 0);      assert (nOffset+nSize <= m_nInfomationSize); 3) 不能使用改变环境的语句, 因为 assert 只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题 错误:  assert (i++ < 100) 这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。 正确:  assert (i < 100);          i++; 4) assert和后面的语句应空一行 ,以形成逻辑和视觉上的一致感 5)有的地方, assert 不能代替条件过滤 ASSERT 只有在Debug版本中才有效,如果编译为Release版本则被忽略掉。(在C中, ASSERT 是宏而不是函数),使用 ASSERT “断言”容易在debug时输出程序错误所在。    而 assert ()的功能类似,它是ANSI C标准中规定的函数,它与 ASSERT 的一个重要区别是可以用在Release版本中。 使用 assert 的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。 在调试结束后,可以通过在包含#include < assert .h>的语句之前插入 #define NDEBUG 来禁用 assert 调用,示例代码如下: [cpp]   view plain   copy   print ? void* memcpy(void *dst, const void *src, size_t count)      {          //安全检查      assert( (dst != NULL) && (src != NULL) );            unsigned char *pdst = (unsigned char *)dst;          const unsigned char *psrc = (const unsigned char *)src;            //防止内存重复      assert(!(psrc<=pdst && pdst<psrc+count));          assert(!(pdst<=psrc && psrc<pdst+count));            while(count--)          {              *pdst = *psrc;              pdst++;              psrc++;          }          return dst;      }    

10.函数指针的用法

int max(int a,int b) {     cout<<a+b<<endl;     return a+b; } int (*p)(int,int)=max; int main() {     (*p)(1,2);  //都可以!     p(1,2); }
函数指针
Int and(int a,int b){return a+b;} int (*p)(int ,int ); p=and; int c=(*p)(1,2);

11.枚举变量

枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。 只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量 enum e{a,b,c=5,d,e} x; int main() { char *s="abcde"; x=(enum e)d; //d代表6,a代表0,b代表1 switch(x) { case a:cout<<"a"<<endl;break; case b:cout<<"b"<<endl;break; case c:cout<<"c"<<endl;break; case d:cout<<"d"<<endl;break; case e:cout<<"e"<<endl;break; } }

12.构造函数 析构函数 是不是虚函数

1. 为什么构造函数不能为虚函数?    虚函数的调用需要虚函数表指针,而该指针存放在对象的内容空间中;若构造函数声明为虚函数, 那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数 ——构造函数了。 2. 为什么析构函数可以为虚函数,如果不设为虚函数可能会存在什么问题?   首先析构函数可以为虚函数,而且当要使用基类指针或引用调用子类时,最好将基类的析构函数声明为虚函数,否则可以存在内存泄露的问题。   举例说明:   子类B继承自基类A; A *p = new B; delete p;   1) 此时,如果类A的析构函数不是虚函数,那么delete p; 将会仅仅调用A的析构函数,只释放了B对象中的A部分,而派生出的新的部分未释放掉。   2) 如果类A的析构函数是虚函数,delete p; 将会先调用B的析构函数,再调用A的析构函数,释放B对象的所有空间。   补充: B *p = new B; delete p;时也是先调用B的析构函数,再调用A的析构函数

13.特殊类型变量

如果一个变量被频繁使用,需保存在寄存器中,因为寄存器的速度要比内存快的许多。在早期的编译器中需要手动定义为register型,但是后来编译器可以自动将调用次数多的变量放入寄存器中。 auto: 默认的分配类型。一般不需要手动声明了,C++11特性;  auto s="abc",ss="edf" //auto变成string auto a=1,*p=2 //相当于aoto变成int auto a=1,c="abc"  //出错,2者类型不一致 static:静态分配内存。变量在整个作用域内是全局变量。 extern : 声明为外部变量;在函数的外部定义变量;
Volatile
修饰符,修饰后的变量可以防止被编译优化,每次取值时会逐语句取值(多线程)

14.二重指针寻址

1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> void f( char **p){        *p += 2 ; } main() {      char *a[] = { "123" , "abc" , "456" },**p;      p = a;      f(p);      printf( "%s\r\n" ,*p); } 输出3 *p+=2;就相当于*p=*p+2; 其中*p指向字符串“123”的第一个元素,即‘1’,指针p向后移两个元素的地址,即指向‘3’ 而*(p+2)才是基于p每次向后移一个字符串的长度,即*(p+2)指向“456”

15.STL一级容器

STL中一级容器是指, 容器元素本身是基本类型, 非组合类型。(vector, deque, list.) set, multiset中元素类型是pair<key_type, key_type>; map, multimap中元素类型是pair<key_type, value_type>;

16.多重继承的优缺点

多继承或者继承都是为了实现类的重用和封装,优点就是减少代码量,实现算法抽象等;缺点(多继承)就是容易造成名字空间冲突(尤其对于MFC类或者其他同一个基类的一般不能使用多继承),或者所称的菱形继承。建议看看《设计模式》这本书,一般可以通过其他方法(聚合等)实现多继承的话应该避免多继承。

17.动态联编(多态)

动态联编就是程序在运行的时候知道该调用哪个函数,而不是编译阶段,所以这个机制应该是由虚函数支持的,即运行时的多态。 基类的某个成员函数声明为虚函数,派生类继承,而且同样重写该函数,那么当声明一个派生类的指针或者引用时,它所调用的函数是由该指针指向的对象确定的,这就是动态联编 多态:指当不同的对象收到相同的消息时,产生不同的动作 编译时多态:函数重载、运算符重载——静态绑定 运行时多态:虚函数机制——动态绑定 C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。

18.转义字符

\0 空字符(NULL) 000  \ddd 任意字符 三位八进制 (不可有大于7的数字!) \xhh 任意字符 二位十六进制  一般转义字符,如‘\b’,由两个字符表示,其实代表一个字符,这个代表退格字符

19.重载的注意事项

重载只要求函数名相同,参数类型和个数不同,对返回值类型不做要求 不能重载‘.’,因为‘.’在类中对任何成员都有意义,已经成为标准用法。    不能重载 ?: ,因为这个运算符对于类对象来说没有实际意义,相反还会引起歧义  还有::

20.指针数组

int fun(int *p[4]), p是包含4个指针的数组。 int a[4][4]作为形参不符合,因为他指向的内容是数组,而不是指针 int **a 二级指针,指针指向的内容还是指针,对的 int D[4][8]  对应的形参为 int(*s)[8] ( 数组指针,每个都指向对应的数组的每列) int D[][8] 1、int(*p)[4];------ ptr 为指向含4个元素的一维整形数组的指针变量(是指针)  对应4个元素数组的指针 2、int *p[4];-------定义指针数组p,它由4个指向整型数据的指针元素组成(是数组) 对应指针的数组 3、int(*)[4];--------实际上可以看作是一种数据类型。也就是第一个(int(*p)[4];)

21.数组取地址的问题

a[]={1,2,3,4,5} 此时,a指代数组的首地址 而&a指代的是也是数组首地址,但是会把数组a看作一个整体 (int *)(&a+1)则指数组最后一个元素的下一个 以下代码的输出是(2  , 5) 1 2 3 int a[ 5 ]={ 1 , 2 , 3 , 4 , 5 }; int *ptr=( int *)(&a+ 1 ); printf( "%d,%d" ,*(a+ 1 ),*(ptr- 1 ));      int a[5]={0,1,2,3,4};     cout<<*(a++)<<endl; //此处a++是错的 作为数组名,a是一个 常量指针 ,a指向的内容可改,但是a指针的指针地址不可改。 应该让int *p=a,那么即可执行p++ 当a传入函数时,退化为指针,那么就可以执行a++了 char *b="abc" 则*b="123"是错的,内容不可改,指向可改。 但b=c是对的。

22.读文件时的定位问题

#include <stdio.h> main()      FILE * fp;      int   i,a[ 6 ]={ 1 , 2 , 3 , 4 , 5 , 6 },k;      fp = fopen( "data.dat" , "w+" );      for   (i= 0 ;i< 6 ;i++)          fseek(fp,0L, 0 );           //移动到文件开头,偏移0           fprintf(fp, "%d\n" ,a[i]);  //           rewind (fp);    //也是回到文件开头          fscanf(fp, "%d" ,&k);      }         fclose(fp);         printf( "%d\n" ,k); } 的输出结果是6 则程序的输出结果是? 如果来看解析的话,估计这道题难点在于fseek和rewind两个函数不太了解。 fseek(文件,偏移量,类别)改变指针函数,,其中类别为文件开头 0,文件当前位置 1,以及文末 2。 所以,fseek(fp,0L,0)就是把文件指针fp移到里开头0字节的地方,即开始位置。 rewind相当于fseek(fp,0L,0),由此可见出题人内心腹黑,强行多考一个函数。 代码循环内流程如下:移到开始位置->写一个数字->移到开始位置->读一个数字(恰恰是刚才写的那个) 循环。

23.结构体大小判断

32位和64位系统的区别在于long和指针,32位下他们是4字节,64位下他们是8字节short都是2字节,float都是4字节,double都是8字节,long long都是8字节short int  2字节long long int 8字节遵循两条原则:一、结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍) 二、结构体大小必须是所有成员大小的整数倍。struct A{ longa1; shorta2; inta3; int*a4;};例如求上面这个结构体A的sizeof大小,在64位下a1的移量为0,长度为8a2的偏移量为0+8=8,长度为2a3的偏移量为8+2=10,不符合int长度4的倍数,故偏移量变成12,,长度为4a4的偏移量为12+4=16,长度为8故总大小为16+8=24,24是所有成员大小的整数倍,故A的大小为24

23.结构体大小判断 #include <stdio.h> main()      FILE * fp;      int   i,a[ 6 ]={ 1 , 2 , 3 , 4 , 5 , 6 },k;      fp = fopen( "data.dat" , "w+" );      for   (i= 0 ;i< 6 ;i++)          fseek(fp,0L, 0 );           //移动到文件开头,偏移0           fprintf(fp, "%d\n" ,a[i]);  //           rewind (fp);    //也是回到文件开头          fscanf(fp, "%d" ,&k);      }         fclose(fp);         printf( "%d\n" ,k); } 的输出结果是6 则程序的输出结果是? 如果来看解析的话,估计这道题难点在于fseek和rewind两个函数不太了解。 fseek(文件,偏移量,类别)改变指针函数,,其中类别为文件开头 0,文件当前位置 1,以及文末 2。 所以,fseek(fp,0L,0)就是把文件指针fp移到里开头0字节的地方,即开始位置。 rewind相当于fseek(fp,0L,0),由此可见出题人内心腹黑,强行多考一个函数。 代码循环内流程如下:移到开始位置->写一个数字->移到开始位置->读一个数字(恰恰是刚才写的那个) 循环。

转载请注明原文地址: https://www.6miu.com/read-2621614.html

最新回复(0)