C++知识库

C++刚学习阶段。相信大家一定也是也是区分了c/c++的区别,那么C++的知识库就先从c和c++的区别开始;

1.指针和数组的区别
2.指针和引用的区别
3.函数重载
4.静多态和动多态
5早绑定和晚绑定(静态绑定和动态绑定)
6.内存泄露
7.malloc底层的实现
8.STL的容器
9.常用的设计模式
10.一个程序从开始编写到最后执行,都发生了什么

 分析问题先找好角度,新颖的角度,全面的回答,会让你的回答显得有深度,有内涵。
 1.指针和数组的区别
   自选角度----汇编和线程
 当参数传递时:数组会被编译器处理成指针
 汇编角度:
    定义:  指针         int *p = NULL;
                    mov    dword ptr [p],0
                   *p = 20;   
                    mov     eax,dword ptr [p]
                    mov    dword ptr [eax],14h

         数组   int arr[10] = {0}; mov    dword ptr [arr],0
                   arr[1] = 10; mov   dword ptr [ebp-34h],0Ah

   指针和数组的定义方面,在汇编上认为他们的定义是相同的; 

数组:定义时:直接将数组首元素给ptr。
      赋值时:在ptr中存放的是首元素地址。等到用的时候,与首元素的地址相加,然后得到地址,直接跳 将值赋给。(跳跃式的查找)。 
指针:定义时:将地址先给寄存器,然后寄存器给ptr,
      赋值时:先将地址给寄存器,然后通过寄存器间接寻址,将值赋到该地址空间。

 
2.指针和引用的区别
汇编角度:
     
char *p = NULL;
008D139E  mov         dword ptr [p],0 

int a = 10;
008D13A5  mov         dword ptr [a],0Ah 
int &b = a;
008D13AC  lea         eax,[a] 
008D13AF  mov         dword ptr [b],eax 
  定义: 指针和引用都是先将值给寄存器,然后再将寄存器的值赋给ptr(本质上是都是指针)
 做参数:指针:传参其实就是值传递,传递的值是地址,被调函数会将指针传进的  形参当作一个局部  
变量,会在栈上开辟一个空间,存放形参。在被调函数内部改变指针,不会影响到主    调函数的值改变。指针当参数的传递会在栈上开辟一个空间,将主调函数的实参值传递,当作是主调实参的副本。
        引用:传参数,也会在栈上开辟空间存放形参的值,但是形参的的任何操作都会影响主调函 
                数的值,可以被看做间接寻址。

                                      

  版本角度
    c++的旧版本:不支持引用a+1,只支持左引用,C++新版本是支持引用a+1,和左右引用。
3.函数重载
   定义:(前提)在相同的作用域下,const修饰指针或者引用时,函数名相同,参数列表不同的函数。不能仅仅依靠返回值类型确定重载。
    如果在编译阶段确定调用哪一个函数时,未找到合适的就开始隐式转换,如果没能隐式转换,就会在报错。
                          
C + +中有一个很通用的作法叫缺省参数。缺省参数就是在用户调用一个函数时没有指定参数值而由编译器插入参数值的参数。
C++将所有所有的函数和参数生成符号,在编译阶段,编译器就必须确定调用哪一个函数。
 为什么要有函数重载?
  ①同一功能的函数,却要命名很多名字。
  ②构造函数和类名相同,如果没有重载函数,那么定义不同的对象,就需要很麻烦。
  ③操作符的重载,本质上也是函数重载,丰富了已有的操作符的含义。

函数重载机制为:作用域+返回类+函数名+参数列表
 
函数的调用匹配:
①精确匹配:参数不做转换,只是做一些微不足道的转换,例如:数组到指针,函数名到指向函数的指针,模板类型的实例化。
②隐式转化的匹配:根据隐式转换图,将部分类型进行转化。
如果匹配结束后,有多个匹配的结果,调用就会被拒绝。

4.静多态和动多态
 函数覆盖:派生类重更新定义基类的某些虚函数,参数和返回类型完全一样
 c++经典就是多态,分为静多态和动多态。
 
 
动多态:
   例子:

class A{ //虚函数示例代码

public:

virtual void fun(){cout<<1<<endl;}

virtual void fun2(){cout<<2<<endl;}

};

class B:public A{

public:

void fun(){cout<<3<<endl;}

void fun2(){cout<<4<<endl;}

};
这两个类中有虚函数的存在,所以编译器会为他们建立一个虚函数表,用于存放该类的所有虚函数的地址。一个指针(虚函数指针)指向这个虚函数表。(一个实例的对象只有一个虚函数指针)

A *p=new A;

p->fun();
工作:这个指针并不能像平通函数那样子直接跳到函数代码,而是先取出虚函数指针,找到虚表,该例子中由于fun是第一个虚函数,所有就存放在第一个虚函数地址。这个地址就是fun的地址。这样子虚函数就可以完成了。
虚函数表(虚函数指针存放在该对象实例的最前面的位置):(父类的虚函数:f(),g(),h()  ,子类的虚函数 f1(),g1(),h1())
注:虚函数表被加载到了只读数据段
一般继承(无虚函数覆盖):虚函数按照声明顺序依次放入虚函数表中,父类在子类的虚函数的前面。
                 
一般继承(有虚函数覆盖):子类将该函数地址重写父类的该函数对应的地址,没有覆盖的函数不变
         
多重继承(无虚函数覆盖):一个父类一个虚函数表,子类的虚函数被放到第一个父类表中(按照声明的第一个父类)
           
多重继承(有虚函数覆盖): 子类将该函数地址重写到 每一个父类的的该函数对应的位置。
            
5早绑定和晚绑定(静态绑定和动态绑定)
     
6.内存泄露
   ①malloc-free /new-delete 只是开辟,不进行释放
   ②在operator= 发生浅拷贝的时候,未释放以前的内存。
   ③new[],释放的时候却delete。
   ④智能指针的交叉引用
   ⑤基类指向堆上的派生类对象(未写成虚函数的析构)
          注:在堆上开辟的派生类对象,如果为写把析构函数写成虚函数,就不会自动析构这个派生类;
               如果写成虚函数的析构,那么就先析构派生类,再析构基类;
   ⑥打开文件未关闭
   ⑦构造函数开辟空间,但是在对象实例以后抛出异常,无法调用析构函数。
   ⑧文件描述符fd的泄露
   ⑨僵尸进程(内存控制块的泄露)
   ⑩函数内部开辟空间,用户一定要手动释放。
         int *fun()
         {
                 int *p = new int;
                  return p;
         }
        int *q = fun();
         delete q;
8.STL的容器
    

vector(2倍增长的空间数组):  push_back  /insert     pop_back/ erase  find/迭代器     预留空间:reserve/resize
                                       reserver(100):预留100个空间,resize(100):直接100个元素
list(双向链表):  push_back/insert    push_front    pop_back/erase   pop_frot  find/迭代器  
 
dequeue(动态开辟的二维数组): push_back/insert    pop_back/erase  
 
set/multiset(红黑树):insert(val)    erase()    find(key)-->不是泛型算法,自己实现的
                       multiset:多重集合key可以重复。
map/multimap(红黑树):insert(make_pair(key,val))   map[key] = val
                      multimap:多重映射表,key可以重复
9.常用的设计模式:单例模式、工厂模式、抽象工厂模式、观察模式
**单利模式--只生成一个对象:构造函数私有,提供一个静态接口,让用户得到这个对象。
  ①实现:
class singleton
{
    public:
        static singleton *getIntance()
        {
            return &object;
        }
    private:
        singleton(){}//构造函数
        static singleton object;
}
singleton singleton::object :
缺点:有一个全局变量,在程序开始的时候就得加载到数据段,无论你开始用不用,都得加载。
②实现
class singleton
{
    public:
        static singleton *getIntance()
        {
            if(pobject == NULL)
            {
                pobject = new singleton();
            }
            return pobject;
        }
    private:
        singleton(){}//构造函数
        static singleton *pobject;
}
singleton *singleton::pobject = NULL :
缺点:但是没有考虑线程安全
③实现:
class singleton
{
    public:
        static singleton *getIntance()
        {
            pthread_mutex_lock(&mutex);
            if(pobject == NULL)
            {
                pobject = new singleton();
            }
            pthread_mutex_unlock(&mutex);
            return pobject;
        }
    private:
        singleton(){}//构造函数
        static singleton *pobject;
}
singleton *singleton::pobject = NULL :
缺点:单线程条件下,每次进入都必须先进行解锁、加锁。浪费时间。
④实现
class singleton
{
    public:
        static singleton *getIntance()
        {

            if(pobject == NULL)
            {
                pthread_mutex_lock(&mutex);
                if(pobject == NULL)
                {
                     pobject = new singleton();
                }
                pthread_mutex_unlock(&mutex);
            }

            return pobject;
        }
    private:
        singleton(){}//构造函数
        static singleton *pobject;
}
singleton *singleton::pobject = NULL :
改进:单线程时,就进入第一个判断,直接返回了,不会再new(只有第一次时,才会进行加锁解锁)。
         多线程时,每一个线程进来都会先进行加锁,产生一个对象,后解锁。
         不会影响速度,效率。

线程安全的问题考虑角度:
多个线程调用:是否存在竟态条件。
竟态条件:随着线程的调度顺序的不同,代码得到的结果不同。
静态条件==》临界区
临界区==》原子操作
原子操作==》互斥锁

































































































  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值