【实习】C++ 基础知识

【实习】C++ 基础知识

OOP

  • 面向对象程序设计 (object-oriented programming)的核心思想是数据抽象、继承和动态绑定(多态)。

虚函数和纯虚函数

虚函数和纯虚函数的区别

  • 虚函数是为了允许用基类的指针或引用来调用子类的这个函数。C++的函数多态就是通过虚函数来实现的 使用关键字 virtual
    虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。、
    派生类必须在内部对所有重新定义的虚函数进行声明,可以在形参列表后增加一个override关键字显示地注明使用哪个成员函数改写基类的虚函数。如果没有覆盖虚函数,则该虚函数的行为等同于其他普通成员,派生类会直接继承其在基类中的版本。
  • 纯虚函数,代表函数没有被实现。为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
    • 定义
        纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
       virtual void funtion1()=0
    • 引入原因
        1)为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
        2)在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
        为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
      声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。
      纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。
      定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。
      纯虚函数的意义,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。
    • 抽象类的介绍
      抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。
      (1)抽象类的定义: 称带有纯虚函数的类为抽象类。
      (2)抽象类的作用:抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。
      (3)使用抽象类时注意: 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。 抽象类是不能定义对象的

总结:
1、纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义(??纯虚函数有一个例子是需要定义的,那就是pure virtual虚构函数。——摘自effective c++ 条款07),纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
2、虚函数声明如下:virtual ReturnType FunctionName(Parameter);虚函数必须实现,如果不实现,编译器将报错,错误提示为:
error LNK****: unresolved external symbol “public: virtual void __thiscall ClassName::virtualFunctionName(void)”
3、对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
4、实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
5、虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
6、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
7、友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
8、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。
C++中一般都是把析构函数声明为虚函数。因为虚函数可以实现动态绑定,也就是到底调用哪个函数是根据指针当前指向哪个对象来确定的,不是根据指针的类型来确定。如果C++中不把析构函数声明为虚函数,那么其有个子类,重写了虚函数,那么当父类指针指向一个子类对象时,当调用析构函数时,只调用父类的析构函数,而无法调用子类的析构函数,所以一般情况是把析构函数声明为虚函数,实现动态绑定。当然如果一个类不包含虚函数,这经常预示不打算将它作为基类使用。当一个类不打算作为基类时,将析构函数声明为虚拟通常是个坏主意。
标准 string 类型不包含虚函数,如果把String作为基类继承得到子类会出问题。
总之:多态基类应该声明虚析构函数。如果一个类有任何虚函数,它就应该有一个虚析构函数;如果不是设计用于做基类或不是设计用于多态,这样的类就不应该声明虚析构函数。
9、有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。

实际上我个人认为纯虚函数的引入,是出于两个目的
1、为了安全,因为避免任何需要明确但是因为不小心而导致的未知的结果,提醒子类去做应做的实现。
2、为了效率,不是程序执行的效率,而是为了编码的效率。

C++虚函数与JAVA中抽象函数比较

C++虚函数与JAVA中抽象函数比较接口与抽象类比较

  • Java中,如果函数不是抽象函数,而是一个普通函数,它是默认实现类似C++中虚函数功能的,也就是说,调用某个函数,是根据当前指针所指向对象的类型来判断的,而不是根据指针类型判断。正好与C++中的普通函数相反。即:JAVA里自动实现了虚函数。
C++ Java
虚函数 普通函数
纯虚函数 抽象函数
抽象类 抽象类
虚基类 接口

多态性

指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a. 编译时多态性:通过重载函数和运算符重载实现。
b. 运行时多态性:通过虚函数和继承实现。

new/delete和malloc/free的区别

new/delete和malloc/free的区别(举例说明)(简单点)

先说说区别:
1. new/delete是C++里才有的。
2. new/delete通常来说是操作符,就是”+”,”-“一样。
3. new/delete是可以重载的,而重载之后,就成为了函数。
4. malloc在申请内存的时候,必须要提供申请的长度,而且返回的指针是void*型,必须要强转成需要的类型。
5. 当new/delete在类中被重载的时候,可以自定义申请过程,比如记录所申请内存的总长度,以及跟踪每个对象的指针。
6. new/delete,其实内部也调用了malloc/free。
共同点:
1. 都必须配对使用。
2. 都是申请内存,释放内存。
3. free和delete可以释放NULL指针。
注意点:
new/delete与malloc/free不能混合使用。
1 我认为new\delete和malloc\free最大区别是对对象的理解。
如果你使用
Foo* foo = malloc(sizeof(Foo));//Foo是一个类
初始化,那么你将不会调用Foo的构造方法,而只是单纯的分配空间。而且我们只认为你是分配一个空间,而不是想创建一个对象。
Foo* foo = new Foo();则会调用Foo的构造方法来初始化对象,也就是说你既要分配空间又要初始化这段空间,让它变成一个对象。
对于delete和free也有同样的问题,就是delete会调用析构函数,free则不会。
说白了,new\delete玩的是对象,而malloc\free仅仅是内存空间而已

2 对于除去对象意外的其他情况,比如int和float等
int* Array=new int[10];和int* Array=malloc(sizeof(int)*10);只存在使用技巧的差别,没有本质的差别。

3 最后也提醒你new\delete和malloc\free只能成对使用,不能混了。
另外 :delete与new配套,delete []与new []配套

数组指针和指针数组的区别

数组指针和指针数组的区别

数组指针(也称行指针)
定义 int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。

如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针。

指针数组
定义 int *p[n];
[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1是错误的,这样赋值也是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:
int *p[3];
int a[3][4];
for(i=0;i<3;i++)
p[i]=a[i];
这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。

这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:(p[i]+j)、((p+i)+j)、(*(p+i))[j]、p[i][j]

c++内存分配方式

C/C++内存管理详解(多看几遍)

在C++中,内存分成5个区,他们分别是:


堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。
自由存储区
自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
全局/静态存储区
全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C++堆栈中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
常量存储区
常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)

堆和栈究竟有什么区别
  好了,我们回到我们的主题:堆和栈究竟有什么区别?
  主要的区别由以下几点:
  (1). 管理方式不同
  (2). 空间大小不同
  (3). 能否产生碎片不同
  (4). 生长方向不同
  (5). 分配方式不同
  (6). 分配效率不同
  管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
  空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:
  打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。
  注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。
  碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。
  生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
  分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
  分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
  从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。
  虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。
  无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候debug可是相当困难的:)

指针与数组的对比

C/C++内存管理详解(多看几遍)


实现内部函数

memcpy、memmove、memset及strcpy函数实现和理解

void *memcpy(void *dest,const void *src,size_t count)
{
    char *pDest=static_cast<char *>(dest);
    const char *pSrc=static_cast<const char *>(src);
    //参数检查
    if ( NULL == dest || NULL == src || count<=0 )
    {
        return NULL;
    }
    if( pDest>pSrc && pDest<pSrc+count )
    {
        for(size_t i=count-1; i>=0; i--)
        {
            pDest[i] = pSrc[i];
        }
    }
    else
    {
        for(size_t i=0; i<count; i++)
        {
            pDest[i] = pSrc[i];
        }
    }
    return pDest;
}

C++多线程,互斥,同步

C++多线程,互斥,同步

深度广度遍历

二叉树 前序、中序、后序、层次遍历及非递归实现 查找、统计个数、比较、求深度的递归实现
二叉树的遍历详解(前序中序后序层次-递归和非递归)


操作系统

线程 进程 线程池

我是一个线程
进程和线程定义,概念,区别详解
线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间

进程

资源分配和调度的基本单位。
进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程

系统独立调度和分派CPU的基本单位。
线程(thread),有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。
线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。
每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

线程、进程区别

1。数据共享(地址空间和其它资源(如打开文件))
进程:数据分开,共享复杂。进程同步和互斥手段的辅助,以保证数据的一致性。
线程: 共享进程数据。(导致同步复杂)
2。通信
进程:进程间通信需要使用IPC
线程:直接读写进程数据段(如全局变量)来进行通信。
3。效率
进程:占用内存多,创建、销毁、切换复杂。CPU利用率低
线程:占用内存少,创建、销毁、切换简单。CPU利用率高。
4。可靠性
进程:相互独立。
线程:一个线程挂掉导致整个进程挂掉。
5。在多线程OS中,进程不是一个可执行的实体。

进程间通信

(1) 管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。
(2)有名管道(named pipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。
(3)信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
(4)消息队列(message queue):消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(5)信号(signal):信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。
(6)共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
(7)套接字(socket):套接口也是一种进程间的通信机制,与其他通信机制不同的是它可用于不同机器间的进程通信。

线程间的通信机制

1、锁机制

 1.1 互斥锁:提供了以排它方式阻止数据结构被并发修改的方法。

 1.2 读写锁:允许多个线程同时读共享数据,而对写操作互斥。

 1.3 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

2、信号量机制:包括无名线程信号量与有名线程信号量

3、信号机制:类似于进程间的信号处理。

线程间通信的主要目的是用于线程同步,所以线程没有象进程通信中用于数据交换的通信机制。

线程池

实现:管理一个任务队列,一个线程队列,然后每次取一个任务分配给一个线程去做,循环往复。
一个线程 pool,一个任务队列 queue ,应该没有意见;
任务队列是典型的生产者-消费者模型,本模型至少需要两个工具:一个 mutex + 一个条件变量,或是一个 mutex + 一个信号量。mutex 实际上就是锁,保证任务的添加和移除(获取)的互斥性,一个条件变量是保证获取 task 的同步性:一个 empty 的队列,线程应该等待(阻塞);
stop 控制任务提交,是受了 Java 的影响,还有实现类不叫 ThreadPool 而是叫 TaskExecutor;
atomic 本身是原子类型,从名字上就懂:它们的操作 load()/store() 是原子操作,所以不需要再加 mutex。
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。

计算机网络

HTTPS和HTTP

http和HTTPS的区别及SSL介绍

HTTP使用TCP三次握手建立连接,客户端和服务器需要交换3个包(具体可查看马海祥博客《HTTP服务的七层架构技术解析及运用》的相关介绍);HTTPS除了TCP的三个包,还要加上ssl握手需要的9个包,所以一共是12个包。
HTTP (全称 Hyper Text Transfer Protocol ),一般称为超文本传输协议,也是互联网上应用最为广泛的一种网络协议。所有的WWW文件都必须遵守这个标准。设计 HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。一种详细规定了浏览器和万维网服务器之间互相通信的规则。 HTTPS (全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是 HTTP 的安全版。
1、HTTP 的 URL 以 HTTP:// 开头,而 HTTPS 的 URL 以 HTTPs:// 开头;
2、HTTP 是不安全的,而 HTTPS 是安全的,比如一些银行、政府、平台网站会使用 HTTPS,提高其安全性。
3、传输效率上 HTTP 要高于 HTTPS ,因为 HTTPS 需要经过加密过程,过程相比于 HTTP 要繁琐一点,效率上低一些也很正常;
4、HTTP 无需证书,而 HTTPS 必需要认证证书;

TCP UDP

TCP协议和UDP协议特性区别总结:
1. TCP协议在传送数据段的时候要给段标号;UDP协议不
2. TCP协议可靠;UDP协议不可靠
3. TCP协议是面向连接;UDP协议采用无连接
4. TCP协议负载较高,采用虚电路;UDP采用无连接
5. TCP协议的发送方要确认接收方是否收到数据段(3次握手协议)
6. TCP协议采用窗口技术和流控制

猜你喜欢

转载自blog.csdn.net/chen_xinjia/article/details/79784634