c++ 一些概念

1、static

静态变量与局部变量、全局变量

  • 全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。其他不包含全局变量定义的源文件需要用extern关键字再次声明这个全局变量。

  • 静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。

  • 静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,但是静态局部变量只对定义自己的函数体可见。

  • 局部变量也只有局部作用域,只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。

  • 全局变量,静态局部变量,静态全局变量都在静态存储区分配空间,而局部变量在栈里分配空间

类静态数据成员

  • 类静态数据成员在编译时创建并初始化:在该类的任何对象建立之前就存在,不属于任何对象,而非静态类成员变量则是属于对象所有的。类静态数据成员只有一个拷贝,为所有此类的对象所共享。

  • static 成员类外存储,求类大小,并不包含在内

  • static 成员只能类外初始化。类外初始化时不需要再加static修饰,因为static是声明性关键字。

静态函数

  • 类静态成员函数属于整个类,不属于某个对象,由该类所有对象共享。

  • 静态成员函数只能访问静态数据成员或静态函数,非静态成员函数可以调用静态函数和静态成员变量。

    • 原因:非静态成员函数,在调用时 this指针时被当作参数传进。而静态成员函数属于类,而不属于对象,没有 this 指针。
  • 静态成员函数可以继承和覆盖,但是不能为虚函数

参考链接:静态变量、全局变量和局部变量

2、const

  • const 修饰变量时表示变量是只读的,不能被修改。

  • const修饰函数承诺在本函数内部不会修改数据成员(包括类内的所有非静态数据成员)。

  • const类成员变量只能在初始化列表中赋值

  • const 函数只能调用 const 函数。非 const 对象和函数都可以调用 const 函数,优先调用非const函数。

    • 因为c++在类的成员函数中还会隐式传入一个指向当前对象的this指针,而在const成员函数传入的则是const 指针,const对象同理
  • 类体外定义的 const 成员函数,在定义和声明处都需要 const 修饰符。

  • const变量储存在栈里

指针使用const

  • 如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量; const char *pContent;
    如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量

    • 指针本身是常量不可变
      char* const pContent;

    • 指针所指向的内容是常量不可变
      const char *pContent;

    • 两者都不可变
      const char* const pContent;

3、inline

为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数。
内联函数省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。
内联函数与宏定义有点类似,都在调用点出进行代码展开,例如

inline int  add(int a, int b) {
    return a+b;
} 

在内部的任何调用add(a,b)的地方都换成了a+b,这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。
与宏定义的区别:内联函数会做安全检查或自动类型转换(同普通函数)
但是
1、inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句例如while、switch,并且不能内联函数本身不能是直接递归函数。

2、inline函数仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思,它如果认为函数不复杂,能在调用点展开

3、建议:inline函数的定义放在头文件中
要求每个调用了内联函数的文件都出现了该内联函数的定义。

4、定义在类中的成员函数缺省都是内联的
如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上inline,否则就认为不是内联的。

5、关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。
例如:

int add(int x, int y);

inline int add(int x, int y) {} // inline 与函数定义体放在一起

6、内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。
如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的提升会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
不要随便地将构造函数和析构函数的定义体放在类声明中,尽量取消不能提升效率的内联。

参考了博客C++中的inline用法

4、friend

  • 友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
  • 类的友元函数是定义在类外部,类外定义时不需再用friend修饰,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数
  • 如果要声明函数或类为一个类的友元,需要在类定义中该函数原型/类前使用关键字 friend
  • 友元函数没有this指针,参数有三种情况:
    • 要访问非static成员时,需要对象做参数;
    • 要访问static成员或全局变量时,则不需要对象做参数;
    • 如果做参数的对象是全局对象,则不需要对象做参数.
    • 友元函数不是其他类的成员函数时,可以直接调用友元函数,不需要通过类的对象或指针
    • 友元关系的性质
      • 友元关系不能被继承
      • 友元关系是单向的
      • 友元关系是不可传递的

4、virtual 虚函数

虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题。

C++的编译器保证虚函数表的指针存在于对象实例中最前面的位置。

//以下代码在32位机器上可成功运行,若64位则需要把int*改成int**
class Base {
     public:
            virtual void f() { cout << "Base::f" << endl; }
            virtual void g() { cout << "Base::g" << endl; }
            virtual void h() { cout << "Base::h" << endl; }

};

typedef void(*Fun)(void);

Base b;

Fun pFun = NULL;

// 取得虚函数表的地址
cout << "虚函数表地址:" << (int*)*(int*)(&b) << endl;

//再次取址就可以得到第一个虚函数的地址
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)*(int*)(&b) << endl;

// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&b));
//输出Base::f
pFun();

//g函数  pFun = (Fun)*((int*)*(int*)(&b)+1);
//h函数  pFun = (Fun)*((int*)*(int*)(&b)+2);

无继承关系时

在有继承关系但无覆盖时

  • 虚函数按照其声明顺序放于表中。
  • 父类的虚函数在子类的虚函数前面。

    有继承关系且覆盖了父类的虚函数时

  • 覆盖的虚函数被放到了虚表中原来父类虚函数的位置。

  • 没有被覆盖的函数依旧。

多重继承无覆盖

  • 每个父类都有自己的虚表。
  • 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

C++ 虚函数表解析(这篇写的太好了!感觉比以前清楚虚函数表了)
C++ 对象的内存布局(这个有点懵)

为什么构造函数不能是虚函数

关于C++为什么不支持虚拟构造函数,Bjarne很早以前就在C++
Style and Technique FAQ里面做过回答:

virtual call is a mechanism to get work done given partial
information. In particular, “virtual” allows us to call a
function knowing only an interfaces and not the exact type of the
object. To create an object you need complete information. In
particular, you need to know the exact type of what you want to
create. Consequently, a “call to a constructor” cannot be
virtual.

  • 就是说虚拟函数调用只需要“部分的”信息,即只需要知道函数接口,而不需要对象的具体类型。
    但是构建一个对象,却必须知道具体的类型信息。

  • 虚函数需要依赖对象中指向类的虚函数表的指针,而这个指针是在构造函数中初始化的(这个工作是编译器做的,对程序员来说是透明的),如果构造函数是虚函数的话,那么在调用构造函数的时候,而此时虚函数表指针并未初始化完成,这就会引起错误。

  • 构造函数、内联函数、友元函数、静态成员函数不能是虚函数

  • 虚函数重载,虚特性丢失

5、struct和class

c++中的struct和class没有特别明显的区别,struct也可以有构造函数和虚构函数,与c语言的结构体不一样,据说是为了兼容结构才保留的struct;

  • struct默认的访问权限是public,class默认的访问权限是private ;

  • class可以用于表示模板类型,struct则不行

  • class和struct如果定义了构造函数,就不能用大括号进行初始化了;若没有定义,struct可以用大括号初始化,而class只有在所有成员变量全是public的情况下,才可以用大括号进行初始化

6、智能指针

由于申请的空间在函数结束时忘记释放,或异常退出导致会造成内存泄漏。
所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

 //管理单个对象时构造函数为explicit
 //unique_ptr和shared_ptr都有Deleter


//unique_ptr的拷贝构造函数只接受典型地由 std::move 生成的右值
//shared_ptr的拷贝构造函数接受const参数


//从 r 转移所有权到 *this ,deleter也要改变
//注意 unique_ptr 的赋值运算符只接受典型地由 std::move 生成的右值。
unique_ptr& operator=( unique_ptr&& r ) noexcept;

//shared_ptr 接受const参数或右值
shared_ptr& operator=( const shared_ptr& r ) noexcept;
shared_ptr& operator=( shared_ptr&& r ) noexcept;

//返回指向被管理对象的指针,如果无被管理对象,则为 nullptr 。
pointer get() const noexcept;

//若存在,则释放被管理对象的所有权, 返回指向被管理对象的指针。调用后 get() 返回 nullptr 。
pointer release() noexcept;

//替换被管理对象,deleter不改变
void reset( pointer ptr = pointer() ) noexcept;

auto_ptr

auto_ptr 是通过由 new 表达式获得的对象,并在 auto_ptr 自身被销毁时删除该对象的智能指针。它可用于为动态分配的对象提供异常安全、传递动态分配对象的所有权给函数和从函数返回动态分配的对象。
复制 auto_ptr ,会复制指针并转移所有权给目标: auto_ptr 的复制构造和复制赋值都会修改其右侧参数,而且“副本”不等于原值。因为这些不常见的复制语义,不可将 auto_ptr 置于标准容器中。

  • auto_ptr不应作为stl容器的元素,因为其不能共享实例,(有些版本的auto_ptr实现中其拷贝构造函数以及拷贝赋值的参数都是non-const类型,导致编译出错),可能导致其指针变为无效指针,
    但实际能否作为容器元素还是与stl实现以及对容器的要求有关

  • 其元素对象的拷贝与原对象不一样

  • auto_ptr不能指向数组,因为其源码的析构函数写的是delete而没有delete[]

  • 使用auto_ptr时避免使用其赋值和拷贝构造函数(const)

auto_ptr到底能不能作为容器的元素?
STL中的智能指针(Smart Pointer)及其源码剖析: std::auto_ptr
unique_ptr

std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针。
在下列两者之一发生时用关联的删除器释放对象:

  • 销毁了管理的 unique_ptr 对象
  • 通过 operator= 或 reset() 赋值另一指针给管理的 unique_ptr 对象。

只有非 const 的 unique_ptr 能转移被管理对象的所有权给另一 unique_ptr 。若对象的生存期为 const std::unique_ptr 所管理,则它被限定在创建指针的作用域中。

  • unique_ptr可以管理单个对象,也可以管理对象数组

std::unique_ptr

shared_ptr

std::shared_ptr 是通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。
下列情况之一出现时销毁对象并解分配其内存:

  • 最后剩下的占有对象的 shared_ptr 被销毁;
  • 最后剩下的占有对象的 shared_ptr 被通过 operator= 或 reset() 赋值为另一指针。

shared_ptr 能在存储指向一个对象的指针时共享另一对象的所有权。此特性能用于在占有其所属对象时,指向成员对象。

引用计数是实现智能指针的一种通用方法。智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象共享同一指针。它的具体做法如下:

  • 当创建类的新对象时,初始化指针,并将引用计数设置为1
  • 当对象作为另一个对象的副本时,复制构造函数复制副本指针,并增加与指针相应的引用计数(加1)
  • 使用赋值操作符对一个对象进行赋值时,处理复杂一点:先使左操作数的指针的引用计数减1(为何减1:因为指针已经指向别的地方),如果减1后引用计数为0,则释放指针所指对象内存。然后增加右操作数所指对象的引用计数(为何增加:因为此时做操作数指向对象即右操作数指向对象)。
  • 析构函数:调用析构函数时,析构函数先使引用计数减1,如果减至0则delete对象。

理解 boost::shared_ptr 中的引用计数是如何工作的
shared_ptr基于引用计数智能指针实现

对三种智能指针都应避免的一点:

string vacation("I wandered lonely as a cloud.");
//pvac过期时,程序将把delete运算符用于非堆内存,这是错误的。
shared_ptr<string> pvac(&vacation);   // No

weak_ptr
弱引用的智能指针weak_ptr是用来监视shared_ptr的,不会使引用计数加一,它不管理shared_ptr内部的指针,主要是为了监视shared_ptr的生命周期,更像是shared_ptr的一个助手。
weak_ptr没有重载运算符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监测权,它的构造不会增加引用计数,它的析构不会减少引用计数,纯粹只是作为一个旁观者来监视shared_ptr中关离得资源是否存在。
weak_ptr还可以用来返回this指针和解决循环引用的问题。

7、声明和定义的区别

声明就是告诉编译器有这么一个类型的变量/返回值、参数列表、函数名的函数,
定义就是为这个变量分配空间/实现这个函数

8、重载overloading,重定义overriding

  • 重载:函数名相同,函数首部(参数表)不同

    • 虚函数重载,虚特性丢失
  • 重定义:函数实部相同,实现不同

9、类型转换

static_cast

  • static_cast较多的使用于基本数据类型之间的转换、基类对象指针(或引用)和派生类对象指针(或引用)之间的转换、一般的指针和void*类型的指针之间的转换等。

  • 通过static_cast只能进行一些相关类型之间的“合理”转换。如果是类类型之间的转换,源类型和目标类型之间必须存在继承关系,否则会得到编译错误。

int a = 1;
float b;
b = static_cast<float>(a);  //类似于C语言中的b = (float)a;

//基类对象指针和派生类对象指针
class A {
    public:
        A(int num) {
            a = num;
        }
        int a;
};

class B : public A {
    public:
        B(int num1, int num2):A(num1) {
            b = num2;
        }
        int b;
};


int main() {

    //基类转换为派生类
    A a(2);
    B* ptrB = static_cast<B*>(&a);
    //输出 2  7274368
    cout << ptrB->a << " " << ptrB->b << endl;

    //派生类转换为基类
    B b(4,-1);
    A* ptrA = static_cast<A*>(&b);
    //输出 4,输出ptrA->b会编译错误
    cout << ptrA->a << endl;

    return 0;
}

dynamic_cast

  • dynamic_cast是一个完全的动态操作符,只能用于指针或者引用间的转换。而且dynamic_cast运算符所操作的指针或引用的对象必须拥有虚函数成员,否则出现编译错误。

  • dynamic_cast可以进行如下的类型转换

    • 在指向基类的指针(引用)与指向派生类的指针(引用)之间进行的转换。
      派生类指针(引用)转换为基类指针(引用)时, 为向上转换,被编译器视为安全的类型转换,也可以使用static_cast进行转换。
      基类指针(引用)转换为派生类指针(引用)为向下转换,被编译器视为不安全的类型转换,需要dynamic_cast进行动态的类型检测。当然,static_cast也可以完成转换,只是存在不安全性。

    • 在多重继承的情况下,派生类的多个基类之间进行转换(称为交叉转换:crosscast)。如父类A1指针实际上指向的是子类,则可以将A1转换为子类的另一个父类A2指针。

class A {
    public:
        virtual void show() {
            cout << "A\n";
        }
};

class B : public A {
    public:
        void show() {
            cout << "B\n";
        }
};


int main() {
    A a;
    //[Warning] dynamic_cast of 'A a' to 'class B*' can never succeed
    B* ptrB = dynamic_cast<B*>(&a);
    //ptrB为nullptr
    if (ptrB) ptrB->show();

    B b;
    A* ptrA = dynamic_cast<A*>(&b);
    //输出B
    ptrA->show();

    return 0;
}

const_cast

  • const_cast主要用于解除常指针和常量的const和volatile属性。也就是说,把cosnt type*转换成type*类型或将const type&转换成type&类型。

  • const_cast取消的是对间接引用时的改写限制(即只针对指针或者引用),而不能改变变量本身的const属性。

const int i = 5;
int j=const_cast<int>(i);
//[Error] invalid use of const_cast with type 'int', 
//which is not a pointer, reference, nor a pointer-to-data-member type

//正确示例如下:
void constTest {
   const int a=5;
   int *p=NULL;
   p=const_cast<int*>(&a);
   (*p)++;
   cout<<a<<endl;//输出6
}

reinterpret_cast

  • 它可以将任意两个无关的指针或引用进行转换,正是因为其过于强大的转换能力,reinterpret_cast是C++语言中最不提倡使用的一种数据类型转换操作符,应该尽量避免使用。

类型转换原则

  • 子类指针(或引用)转换为父类指针(或引用)编译器认为总是是安全的,即向上转换,请使用static_cast,而非dynamic_cast,原因是static_cast效率高于dynamic_cast。

  • 父类指针(或引用)转换为子类指针(或引用)时存在风险,即向下转换,必须使用dynamic_cast进行动态类型检测。

  • 总领性原则:不要使用C风格的强制类型转换,而是使用标准C++中的四个类型转换符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。

C/C++数据类型的转换之终极无惑

10、内存对齐

  • 对于结构的各个成员,第一个成员位于偏移为0的位置,以后每个数据成员的偏移量必须是min(#pragma pack()指定的数,这个数据成员的自身长度) 的倍数。

  • 在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

  • #pragma pack(n)作为一个预编译指令用来设置多少个字节对齐的。值得注意的是,n的缺省数值是按照编译器自身设置,一般为8,合法的数值分别是1、2、4、8、16。
    即编译器只会按照1、2、4、8、16的方式分割内存。若n为其他值,是无效的。

#include<iostream>
using namespace std;
#pragma pack(1)//设定为 1 字节对齐
class test {
private :

    char c='1';//1byte 
    int i;//4byte
    short s=2;//2byte
};

class test2 {
private:
    int i;//4byte
    char c = '1';//1byte 
    short s = 2;//2byte
};
int main(){
    //输出7
    cout << sizeof(test) << endl;
    //输出7
    cout << sizeof(test2) << endl;
    return 0;
}

C++内存对齐总结
内存对齐的规则以及作用

11、运算符重载

  • 运算符可看作函数
    operand1 op operand2 可理解为
    op(operand1,operand2)

  • 一元运算符是一个具有一个参数的函数,
    二元运算符是一个具有两个参数的函数

类运算符重载

  • 重载二元运算符时,成员运算符函数只需显示传递一个参数(即二元运算符的右操作数),而左操作数则是对该类对象本身,通过this指针隐式传递。

  • 重载一元运算符时,成员运算符没有参数,操作数是该类对象本身,通过this指针隐式传递。

class COMPLEX {
    public:
        COMPLEX(double r = 0, double i = 0);
        COMPLEX(const COMPLEX& other);
        COMPLEX operator + (const COMPLEX& other);
        COMPLEX operator - (const COMPLEX& other);
        COMPLEX operator = (const COMPLEX& other);
    protected:
        double real,image;
};
//定义
COMPLEX::COMPLEX(double r, double i) {
    real = r;
    image = i;
}

COMPLEX::COMPLEX(const COMPLEX& other) {
    real = other.real;
    image = other.image;
}

COMPLEX COMPLEX::operator + (const COMPLEX& other) {
    COMPLEX temp;
    temp.real = real+other.real;
    temp.image = image+other.image;
    return temp;
}

COMPLEX COMPLEX::operator - (const COMPLEX& other) {
    COMPLEX temp;
    temp.real = real-other.real;
    temp.image = image-other.image;
    return temp;
}

/*若函数的返回值类型为COMPLEX,则此语句会引起对拷贝构造函数的调用;
若函数的返回值类型为 COMPLEX&,则不调用拷贝构造函数*/
COMPLEX& COMPLEX::operator = (const COMPLEX& other) {
    real = other.real;
    image = other.image;
    return *this;
}
  • 赋值运算符的重载
    • 若某个类没有重载赋值运算符,则编译器将自动生成一个缺省的赋值运算符,缺省赋值运算符 采用浅复制策略,把源对象逐位拷贝到目标对象。
STRING& STRING::operator=(const STRING &other) {
    if (this == &other)
        return *this;

    if (other.buffer == NULL) {
        buffer = NULL;
        return *this;
    }

    length = other.length;
    if (buffer != NULL)
        delete []buffer;
    buffer = new char[length+1];
    if (buffer != NULL)
        strcpy(buffer,other.buffer);
    return *this;
}

下标运算符[ ]的重载

int& operator [] (int index) {
    if ((index < 0) || (index >MAX_SIZE-1)) {
        cout << "index out of bounds.\n";
        exit(1);
    }
    return table[index];
}
  • 下标运算符不能作为友元重载

重载<< 和 >>

class COMPLEX {
    public:
        friend ostream& operator <<(ostream& out, const COMPLEX& x);
        friend istream& operator >>(istream& in, COMPLEX& x);
    protected:
        double real,image;
};
ostream& operator << (ostream& out, const COMPLEX& x) {
    out << "real: " << x.real << endl;
    out << "image: " << x.image << endl;
    return out;
}


istream& operator >> (istream& in, COMPLEX& x) {
    cout << "Please input the real part\n";
    in >> x.real;
    cout << "Please input the image part\n";
    in >> x.image;
    return in;
}

重载前置和后置++,- -

class COMPLEX {
    public:
        COMPLEX& operator++();//前置++ 
        COMPLEX operator++(int);//后置++ 
        COMPLEX& operator--();//前置-- 
        COMPLEX operator--(int);//后置--
    protected:
        double real,image;
};
//前置++
COMPLEX& COMPLEX::operator++() {
    real += 1;
    image += 1;
    return *this; 
} 
//后置++
COMPLEX COMPLEX::operator++(int) {
    COMPLEX before = *this;
    real += 1;
    image += 1;
    return before;
}
//前置--
COMPLEX& COMPLEX::operator--() {
    real -= 1;
    image -= 1;
    return *this; 
}
//后置--
COMPLEX COMPLEX::operator--(int) {
    COMPLEX before = *this;
    real -= 1;
    image -= 1;
    return before;
}

12、构造函数、拷贝构造函数、析构函数

构造函数

  • 如何向构造函数传递参数:在声明类对象时,在对象名后直接写 上实参列表,编译器就会根据实参的个数和类型选择调用合适的构造函数

  • 若声明类对象时没有实参列表,则:

    • 若该类有缺省构造函数(即无参构造函数) ,则调用该构造函数,同时创建该对象(为该对象分配内存空间) 。
    • 若该类没有缺省构造函数,则创建该对象。但该对象的私有成员 没有得到初始化。
  • 如果设计的类没有构造函数,C++编译器会自动为该类 型建立一个缺省构造函数。该构造函数没有任何形参, 且函数体为空。

拷贝构造函数

  • 形参类型为该类类型本身,且参数传递方式为按引用传递,避免在函数调用过程中生成形参副本。
    该形参一般声明为const,以确保在拷贝构造函数中不修改实参的值
C::C(const  C& obj); 
//例如: 
COMPLEX(const COMPLEX& other);
  • 用一个已存在的该类对象初始化新创建的对象。

  • 每个类都必须有拷贝构造函数:

    • 用户可根据自己的需要显式定义拷贝构造函数。

    • 若用户未提供,则该类使用由系统提供的缺省拷贝构造函数。

    • 缺省拷贝构造函数使用逐位复制方式利用已存在的对象来初始化新创建的对象(相当于赋值=)。

拷贝构造函数的作用

  • 在声明语句中用一个对象初始化另一个对象
//假定C为已定义的类,则 
C  obja; //调用C的缺省构造函数 
C  obja(1,2) //调用C的有参普通构造函数

/*调用C的拷贝构造函数用对象obja初始化对象objb。
如果有为C类明确定义拷贝构造函数,将调用这个拷贝构造函数;
如果没有为C类定义拷贝构造函数,将调用缺省的拷贝构造函数。 
*/ 
C objb(obja); //或 
C  objb = obja ;  //两者等价
  • 将一个对象作为实参,以按值调用方式传递给被调函数的 形参对象。
    这里写图片描述

  • 生成一个临时对象作为函数的返回结果:当函数返回一对象时,系 统将自动创建一个临时对象来保存函数的返回值。创建此临时对象 时调用拷贝构造函数,当函数调用表达式结束后,撤销该临时对象 时,调用析构函数。
    这里写图片描述

  • 验证这个作用时用dev c++没有验证出来,但是用vs2015可以看到生成临时变量会调用拷贝构造函数。
    应该是编译器的差异

析构函数

  • 析构函数不能有任何返回类型,这点与构造函数相同。 但同时析构函数还不能带任何参数,也就是说析构函数 一定是无参函数。

  • 在客户代码中,可以通过“.”显式调用析构函数;但更多 的情况下,是在对象生存期结束时自动被调用的。

13、设计模式

单例模式

  • 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 主要解决:一个全局使用的类频繁地创建与销毁。
  • 何时使用:当您想控制实例数目,节省系统资源的时候。
  • 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
  • 关键代码:构造函数是私有的。

工厂模式

  • 意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
  • 主要解决:主要解决接口选择的问题。
  • 何时使用:我们明确地计划不同条件下创建不同实例时。
  • 如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
  • 关键代码:创建过程在其子类执行。

迭代器模式

  • 意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
  • 主要解决:不同的方式来遍历整个整合对象。
  • 何时使用:遍历一个聚合对象。

观察者模式

  • 意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  • 主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
  • 何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

设计模式简介:菜鸟教程

GUI->组合模式
数据结构遍历->迭代器
消息机制->观察者模式
资源管理->单例模式
stl的stack、queue、priority_queue ->adapter适配器模式

猜你喜欢

转载自blog.csdn.net/unirrrrr/article/details/80428529