C++笔记整理

把自己印象笔记中所记录的一些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>
返回类型 函数名(形参表)
{//函数定义体 }

  1. //声明一个函数模版,用来比较输入的两个相同数据类型的参数的大小,class也可以被typename代替,  
  2. //T可以被任何字母或者数字代替。  
  3. template <class T>  
  4. T min(T x,T y)  
  5. {  
  6.     return(x<y)?x:y;  
  7. }  

模板类和重载函数一起使用时:
     两者一起使用时, 先考虑重载函数 后考虑模板类 ,如过再找不到,就考虑类型转换,可能会带来精度的变化。
    
类模板:
  1. template <class T>  
  2. class Base  
  3. {  
  4. public :      
  5.     T a ;  
  6.     Base(T b)  {  
  7.         a = b ;      
  8.     }     
  9.     T getA(){ return a ;} //类内定义   
  10.     void setA(T c);  
  11. };  
  12.   
  13. template <class T>   //模板在类外的定义   
  14. void  Base<T>::setA(T c)  
  15. {  
  16.     a = c ;  
  17. }  


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 ? 派生到我的代码片
  1. void* memcpy(void *dst, const void *src, size_t count)      
  2. {      
  3.     //安全检查  
  4.     assert( (dst != NULL) && (src != NULL) );      
  5.   
  6.     unsigned char *pdst = (unsigned char *)dst;      
  7.     const unsigned char *psrc = (const unsigned char *)src;      
  8.   
  9.     //防止内存重复  
  10.     assert(!(psrc<=pdst && pdst<psrc+count));      
  11.     assert(!(pdst<=psrc && psrc<pdst+count));      
  12.   
  13.     while(count--)      
  14.     {      
  15.         *pdst = *psrc;      
  16.         pdst++;      
  17.         psrc++;      
  18.     }      
  19.     return dst;      
  20. }    



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,长度为8
a2的偏移量为0+8=8,长度为2
a3的偏移量为8+2=10,不符合int长度4的倍数,故偏移量变成12,,长度为4
a4的偏移量为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),由此可见出题人内心腹黑,强行多考一个函数。
代码循环内流程如下:移到开始位置->写一个数字->移到开始位置->读一个数字(恰恰是刚才写的那个) 循环。




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值