STL学习记录

c++ STL总结(算是吧)
给各位蒟蒻兄弟在NOIP中有的一用

1.STL简介
STL的原名是“Standard Template Library”,翻译过来就是标准模板库。STL是C++标准库的一个重要组成部分,主要由六大组件构成。这六大组件是:
容器(Container)、算法(algorithm)、迭代器(iterator)、仿函数(functor)、适配器(adapter)、配置器(allocator)。

1、容器(container)
容器可以分为三类即序列容器、关联容器和容器适配器。各类具体包含如下所示:
序列容器:vector、list、deque
关联容器:set、map、multiset、multimap
适配器容器:stack、queue、priority_queue


://几个重要的容器,转载自
https://www.cnblogs.com/tgycoder/p/5379096.html

2、算法(Algorithm)
算法部分主要在头文件algorithm,numeric,functional中。algorithm是所有STL头文件中最大的一个,它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范 围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。numeric体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。functional中则定义了一些模板类,用以声明函数对象。

3、迭代器(Adapter)
迭代器是用类模板(class template)实现的.重载了* ,-> ,++ ,– 等运算符。
迭代器分5种:输入迭代器、输出迭代器、 前面迭代器、双向迭代器、 随机访问迭代器。
输入迭代器:向前读(只允许读);
输出迭代器:向前写(只允许写);
前向迭代器:向前读写;
双向迭代器:向前后读写;
随机迭代器:随机读写;

4、仿函数(Functor)
仿函数用类模板实现,重载了符号”()”。仿函数,又或叫做函数对象,是STL六大组件之一;仿函数虽然小,但却极大的拓展了算法的功能,几乎所有的算法都有仿函数版本
例如,查找算法find_if就是对find算法的扩展,标准的查找是两个元素相等就找到了,但是什么是相等在不同情况下却需要不同的定义,如地址相等,地址和邮编都相等,虽然这些相等的定义在变,但算法本身却不需要改变,这都多亏了仿函数。仿函数(functor)又称之为函数对象(function object),其实就是重载了()操作符的struct,没有什么特别的地方。

如以下代码定义了一个二元判断式functor:

struct IntLess
{
    bool operator()(int left, int right) const
    {
        return (left < right);
    }
};

仿函数的优势:

1)仿函数比一般函数灵活。

2)仿函数有类型识别。可以用作模板参数。

3)执行速度上仿函数比函数和指针要更快。

在STL里仿函数最常用的就是作为函数的参数,或者模板的参数。
在STL里有自己预定义的仿函数,比如所有的运算符=,-,*,、比如’<’号的仿函数是less。

       // TEMPLATE STRUCT less
template<class _Ty = void>
    struct less
        : public binary_function<_Ty, _Ty, bool>
    {    // functor for operator<
     bool operator()(const _Ty& _Left, const _Ty& _Right) const
        {    // apply operator< to operands
            return (_Left < _Right);
        }
    };

less继承binary_function<_Ty,_Ty,bool>

template<class _Arg1, class _Arg2, class _Result>
struct binary_function
{ // base class for binary functions
        typedef _Arg1 first_argument_type;
        typedef _Arg2 second_argument_type;
        typedef _Result result_type;
};

从定义中可以知道binary_function知识做了一些类型的声明,这样做就是为了方便安全,提高可复用性。

按照这个规则,我们也可以自定义仿函数:

template <typename type1,typename type2>
class func_equal :public binary_function<type1,type2,bool>
{
        inline bool operator()(type1 t1,type2 t2) const//这里的const不能少
        {
                 return t1 == t2;//当然这里要overload==
        }
}

之所以const关键字修饰函数,是因为const对象只能访问const修饰的函数。如果一个const对象想使用重载的()函数,编译过程就会报错。

小结一下:仿函数就是重载()的class,并且重载函数要有const修饰。自定义仿函数必须要继承binary_function(二元函数)或者unary_function(一元函数)。其中unary_function的定义如下:

struct unary_function { 
    typedef _A argument_type; 
    typedef _R result_type; 
};

5、适配器(Adapter)
适配器是用来修改其他组件接口的STL组件,是带有一个参数的类模板(这个参数是操作的值的数据类型)。STL定义了3种形式的适配器:容器适配器,迭代器适配器,函数适配器。

1)容器适配器:栈(stack)、队列(queue)、优先(priority_queue)。使用容器适配器,stack就可以被实现为基本容器类型(vector,dequeue,list)的适配。可以把stack看作是某种特殊的vctor,deque或者list容器,只是其操作仍然受到stack本身属性的限制。queue和priority_queue与之类似。容器适配器的接口更为简单,只是受限比一般容器要多。

2)迭代器适配器:修改为某些基本容器定义的迭代器的接口的一种STL组件。反向迭代器和插入迭代器都属于迭代器适配器,迭代器适配器扩展了迭代器的功能。

3)函数适配器:通过转换或者修改其他函数对象使其功能得到扩展。这一类适配器有否定器(相当于"非"操作)、绑定器、函数指针适配器。函数对象适配器的作用就是使函数转化为函数对象,或是将多参数的函数对象转化为少参数的函数对象。

例如:

在STL程序里,有的算法需要一个一元函数作参数,就可以用一个适配器把一个二元函数和一个数值,绑在一起作为一个一元函数传给算法。 

    find_if(coll.begin(), coll.end(), bind2nd(greater <int>(), 42)); 
这句话就是找coll中第一个大于42的元素。 
    greater <int>(),
其实就是">"号,是一个2元函数 
bind2nd的两个参数,要求一个是2元函数,一个是数值,结果是一个1元函数。bind2nd就是个函数适配器。

6、空间配置器(Allocator)
STL内存配置器为容器分配并管理内存。统一的内存管理使得STL库的可用性、可移植行、以及效率都有了很大的提升。

SGI-STL的空间配置器有2种,一种仅仅对c语言的malloc和free进行了简单的封装,而另一个设计到小块内存的管理等,运用了内存池技术等。在SGI-STL中默认的空间配置器是第二级的配置器.
SGI使用时std::alloc作为默认的配置器。

alloc把内存配置和对象构造的操作分开,分别由alloc::allocate()和::construct()负责,同样内存释放和对象析够操作也被分开分别由alloc::deallocate()和::destroy()负责。这样可以保证高效,因为对于内存分配释放和构造析够可以根据具体类型(type traits)进行优化。比如一些类型可以直接使用高效的memset来初始化或者忽略一些析构函数。对于内存分配alloc也提供了2级分配器来应对不同情况的内存分配。
第一级配置器直接使用malloc()和free()来分配和释放内存。第二级视情况采用不同的策略:当需求内存超过128bytes的时候,视为足够大,便调用第一级配置器;当需求内存小于等于128bytes的时候便采用比较复杂的memeory pool的方式管理内存。
无论allocal被定义为第一级配置器还是第二级,SGI还为它包装一个接口,使得配置的接口能够符合标准即把配置单位从bytes转到了元素的大小:
template<class T, class Alloc>
class simple_alloc
{
public:
     static T* allocate(size_t n)
     {
         return 0 == n ? 0 : (T*)Alloc::allocate(n * sizeof(T));
     }

     static T* allocate(void)
     {
         return (T*) Alloc::allocate(sizeof(T));
     }

     static void deallocate(T* p, size_t n)
     {
         if (0 != n) Alloc::deallocate(p, n * sizeof(T));
     }

     static void deallocate(T* p)
     {
         Alloc::deallocate(p, sizeof(T));
     }
}

内存的基本处理工具,均具有commit或rollback能力。

template<class InputIterator, class ForwardIterator>
ForwardIterator
uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result);

template<class ForwardIterator, class T>
void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x);

template<class ForwardIterator, class Size, class T>
ForwardIterator
uninitialized_fill_n(ForwardIterator first, ForwardIterator last, const T& x)

泛型技术
泛型技术的实现方法有:模板、多态等。模板是编译时决定的,多态是运行时决定的,RTTI也是运行时确定的。
多态是依靠虚表在运行时查表实现的。比如一个类拥有虚方法,那么这个类的实例的内存起始地址就是虚表地址,可以把内存起始地址强制转换成int*,取得虚表,然后(int*)(int)取得虚表里的第一个函数的内存地址,然后强制转换成函数类型,即可调用来验证虚表机制。
泛型编程(Generic Programming,以下直接以GP称呼)是一种全新的程序设计思想,和OO,OB,PO这些为人所熟知的程序设计想法不同的是GP抽象度更高,基于GP设计的组件之间耦合度低,没有继承关系,所以其组件间的互交性和扩展性都非常高。我们都知道,任何算法都是作用在一种特定的数据结构上的,最简单的例子就是快速排序算法最根本的实现条件就是所排序的对象是存贮在数组里面,因为快速排序就是因为要用到数组的随机存储特性,即可以在单位时间内交换远距离的对象,而不只是相临的两个对象,而如果用链表去存储对象,由于在链表中取得对象的时间是线性的即O[n],这样将使快速排序失去其快速的特点。也就是说,我们在设计一种算法的时候,我们总是先要考虑其应用的数据结构,比如数组查找,联表查找,树查找,图查找其核心都是查找,但因为作用的数据结构不同将有多种不同的表现形式。数据结构和算法之间这样密切的关系一直是我们以前的认识。泛型设计的根本思想就是想把算法和其作用的数据结构分离,也就是说,我们设计算法的时候并不去考虑我们设计的算法将作用于何种数据结构之上。泛型设计的理想状态是一个查找算法将可以作用于数组,联表,树,图等各种数据结构之上,变成一个通用的,泛型的算法。

六大组件的关系结构
这里写图片描述
Container 通过Allocator获得数据存储空间;Algorithm 通过Iterator存取Container中的内容;Functor可以协助Algoritm完成不同策略;Adapter可以修饰Container、Algorithm、Iterator。

C++类(Class)总结

一、C++类的定义
C++中使用关键字 class 来定义类, 其基本形式如下:
    class 类名
    {
        public://行为或属性 
        protected://行为或属性
        private://行为或属性
    };

示例:
定义一个点(Point)类, 具有以下属性和方法:
■ 属性: x坐标, y坐标
■ 方法: 1.设置x,y的坐标值; 2.输出坐标的信息。
实现代码:

class Point
{
public:
     void setPoint(int x, int y);
     void printPoint();

private:
     int xPos;
     int yPos;
};  

代码说明:
上段代码中定义了一个名为 Point 的类, 具有两个私密属性, int型的xPos和yPos, 分别用来表示x点和y点。
在方法上, setPoint 用来设置属性, 也就是 xPos 和 yPos 的值; printPoint 用来输出点的信息。

           ==============我是分割线==============

1 数据抽象和封装

 抽象是通过特定的实例抽取共同特征以后形成概念的过程。一个对象是现实世界中一个实体的抽象,一个类是一组对象的抽象。
 封装是将相关的概念组成一个单元,然后通过一个名称来引用它。面向对象封装是将数据和基于数据的操作封装成一个整体对象,对数据的访问或修改只能通过对象对外提供的接口进行。

2 类定义

    几个重要名词:
    (1) 类名
         遵循一般的命名规则; 字母,数字和下划线组合,不要以数字开头。
    (2) 类成员
         类可以没有成员,也可以定义多个成员。成员可以是数据、函数或类型别名。所有的成员都必须在类的内部声明。没有成员的类是空类,空类也占用空间。

    > class People
    {
    };
    sizeof(People) = 1; //定义类成员

    (3) 构造函数
     构造函数是一个特殊的、与类同名的成员函数,用于给每个数据成员设置适当的初始值。
    (4) 成员函数
     成员函数必须在类内部声明,可以在类内部定义,也可以在类外部定义。如果在类内部定义,就默认是内联函数。

3 类定义补充

3.1 可使用类型别名来简化类
    除了定义数据和函数成员之外,类还可以定义自己的局部类型名字。
 使用类型别名有很多好处,它让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。
class People
{ 
public: 
     typedef std::string phonenum; //电话号码类型

     phonenum phonePub; //公开号码
private:      
     phonenum phonePri;//私人号码
}; 
3.2 成员函数可被重载
 可以有多个重载成员函数,个数不限。
3.3 内联函数
 有三种:
    (1)直接在类内部定义。
    (2)在类内部声明,加上inline关键字,在类外部定义。
    (3)在类内部声明,在类外部定义,同时加上inline关键字。注意:此种情况下,内联函数的定义通常应该放在类定义的同一头文件中,而不是在源文件中。这是为了保证内联函数的定义在调用该函数的每个源文件中是可见的。
3.4 访问限制
     public,private,protected 为属性/方法限制的关键字。
3.5 类的数据成员中不能使用 auto、extern和register等进行修饰, 也不能在定义时进行初始化
 如 int xPos = 0; //错;
    例外:
      静态常量整型(包括char,bool)数据成员可以直接在类的定义体中进行初始化,例如:
      static const int ia= 30; 

4 类声明与类定义

4.1 类声明(declare)
class Screen;
      在声明之后,定义之前,只知道Screen是一个类名,但不知道包含哪些成员。只能以有限方式使用它,不能定义该类型的对象,只能用于定义指向该类型的指针或引用,声明(不是定义)使用该类型作为形参类型或返回类型的函数。
    void Test1(Screen& a){};
    void Test1(Screen* a){};
4.2 类定义(define)
 在创建类的对象之前,必须完整的定义该类,而不只是声明类。所以,类不能具有自身类型的数据成员,但可以包含指向本类的指针或引用。
class LinkScreen
{
public:
          Screen window;
          LinkScreen* next;
          LinkScreen* prev;
}; //注意,分号不能丢
     因为在类定义之后可以接一个对象定义列表,可类比内置类型,定义必须以分号结束:
class LinkScreen{ /* ... */ };
class LinkScreen{ /* ... */ } scr1,scr2; 

5 类对象

  定义类对象时,将为其分配存储空间。
 Sales_item item; //编译器分配了足以容纳一个 Sales_item 对象的存储空间。item 指的就是那个存储空间。

6 隐含的 this 指针
成员函数具有一个附加的隐含形参,即 this指针,它由编译器隐含地定义。成员函数的函数体可以显式使用 this 指针。

6.1 何时使用 this 指针
     当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。最常见的情况是在这样的函数中使用 this:该函数返回对调用该函数的对象的引用。
class Screen 
{
...
public:
      Screen& set(char);
};
Screen& Screen::set(char c) 
{
      contents[cursor] = c;
      return *this;
}

7 类作用域

 每个类都定义了自己的作用域和唯一的类型。
 类的作用域包括:类的内部(花括号之内), 定义在类外部的成员函数的参数表(小括号之内)和函数体(花括号之内)。
class Screen 
{ 
//类的内部
...
}; 
//类的外部
char Screen::get(index r, index c) const
{
     index row = r * width;// compute the row location
     return contents[row + c];
     // offset by c to fetch specified character
} 
 注意:成员函数的返回类型不一定在类作用域中。可通过 类名::来判断是否是类的作用域,::之前不属于类的作用域,::之后属于类的作用域。例如Screen:: 之前的返回类型就不在类的作用域,Screen:: 之后的函数名开始到函数体都是类的作用域。
class Screen 
{ 
    public: 
    typedef std::string::size_type index; 
     index get_cursor() const; 
}; 
Screen::index Screen::get_cursor() const
   //注意:index前面的Screen不能少
{ 
     return cursor; 
} 
 该函数的返回类型是 index,这是在 Screen 类内部定义的一个类型名。在类作用域之外使用,必须用完全限定的类型名 Screen::index 来指定所需要的 index 是在类 Screen 中定义的名字。

二 构造函数
构造函数是特殊的成员函数,用来保证每个对象的数据成员具有合适的初始值。
构造函数名字与类名相同,不能指定返回类型(也不能定义返回类型为void),可以有0-n个形参。
在创建类的对象时,编译器就运行一个构造函数。

1 构造函数可以重载
 可以为一个类声明的构造函数的数量没有限制,只要每个构造函数的形参表是唯一的。
class Sales_item;
{
public: 
     Sales_item(const std::string&); 
     Sales_item(std::istream&); 
     Sales_item(); //默认构造函数
}; 
2 构造函数自动执行 
     只要创建该类型的一个对象,编译器就运行一个构造函数:
Sales_item item1("0-201-54848-8");
Sales_item *p = new Sales_item(); 
 第一种情况下,运行接受一个 string 实参的构造函数,来初始化变量item1。
 第二种情况下,动态分配一个新的 Sales_item 对象,通过运行默认构造函数初始化该对象。

3 构造函数初始化式
     与其他函数一样,构造函数具有名字、形参表和函数体。
 与其他函数不同的是,构造函数可以包含一个构造函数初始化列表:  
        Sales_item::Sales_item(const string &book): isbn(book), units_sold(0), revenue(0.0)
{ } 
 构造函数初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。
 构造函数可以定义在类的内部或外部。构造函数初始化只在构造函数的定义中指定。
 构造函数分两个阶段执行:(1)初始化阶段;(2)普通的计算阶段。初始化列表属于初始化阶段(1),构造函数函数体中的所有语句属于计算阶段(2)。
 初始化列表比构造函数体先执行。不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。
3.1 哪种类需要初始化式
 const 对象或引用类型的对象,可以初始化,但不能对它们赋值,而且在开始执行构造函数的函数体之前要完成初始化。
 初始化 const 或引用类型数据成员的唯一机会是构造函数初始化列表中,在构造函数函数体中对它们赋值不起作用。
 没有默认构造函数的类类型的成员,以及 const 或引用类型的成员,必须在初始化列表中完成初始化。
 class ConstRef 

{
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
};
ConstRef::ConstRef(int ii)
{
i = ii; // ok
ci = ii; // error
ri = i; //
}
应该这么初始化:
ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { }
3.2 成员初始化的次序
每个成员在构造函数初始化列表中只能指定一次。重复初始化,编译器一般会有提示。
成员被初始化的次序就是定义成员的次序,跟初始化列表中的顺序无关。
3.3 初始化式表达式
初始化式可以是任意表达式
Sales_item(const std::string &book, int cnt, double price): isbn(book), units_sold(cnt), revenue(cnt * price) { }
3.4 类类型的数据成员的初始化式
初始化类类型的成员时,要指定实参并传递给成员类型的一个构造函数,可以使用该类型的任意构造函数。
Sales_item(): isbn(10, ‘9’), units_sold(0), revenue(0.0) {}
3.5 类对象的数据成员的初始化
在类A的构造函数初始化列表中没有显式提及的每个成员,使用与初始化变量相同的规则来进行初始化。
类类型的数据成员,运行该类型的默认构造函数来初始化。
内置或复合类型的成员的初始值依赖于该类对象的作用域:在局部作用域中不被初始化,在全局作用域中被初始化为0。假设有一个类A,
class A
{
public:
int ia;
B b;
};
A类对象A a;不管a在局部作用域还是全局作用域,b使用B类的默认构造函数来初始化,ia的初始化取决于a的作用域,a在局部作用域,ia不被初始化,a在全局作用域,ia初始化0。

4 默认构造函数
不含形参的构造函数就是默认构造函数。
只要定义一个对象时没有提供初始化式,就使用默认构造函数。如: A a;
为所有形参提供默认实参的构造函数也定义了默认构造函数。例如:
class A
{
public:
A(int a=1,char c =”){}
private:
int ia;
char c1;
};
4.1 合成的默认构造函数
只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
一个类只要定义了一个构造函数,编译器也不会再生成默认构造函数。
建议:
如果定义了其他构造函数,也提供一个默认构造函数。
如果类包含内置或复合类型(如 int& 或 string*)的成员,它应该定义自己的构造函数来初始化这些成员。每个构造函数应该为每个内置或复合类型的成员提供初始化。

5 隐式类类型转换
5.1 只含单个形参的构造函数能够实现从形参类型到该类类型的一个隐式转换
class A
{
public:
A(int a)
{
ia =a;
}

 bool EqualTo(const A& a)
 {
      return ia == a.ia;
 }

private:
int ia;
};

A a(1);
bool bEq = false;
bEq = a.EqualTo(1);//参数为1,实现从int型到A的隐式转换

5.2抑制由构造函数定义的隐式转换
通过将构造函数声明为 explicit,来防止在需要隐式转换的上下文中使用构造函数:
class A
{
public:
explicit A(int a )
{
ia =a;
}

 bool EqualTo(const A& a)
 {
      return ia == a.ia;
 }

private:
 int ia;
};

通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为 explicit。将构造函数设置为 explicit 可以避免错误。

三 复制控制
1 复制构造函数
1.1 几个要点
(1) 复制构造函数
复制构造函数是一种特殊构造函数,只有1个形参,该形参(常用 const &修饰)是对该类类型的引用。
class Peopel
{
public:
Peopel();//默认构造函数
Peopel(const Peopel&);//复制构造函数
~Peopel();//析构函数
};
当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数。
Peopel a1; Peopel a2 = a1;
当将该类型的对象传递给函数或函数返回该类型的对象时,将隐式使用复制构造函数。
Peopel Func(Peopel b){…}
(2)析构函数
析构函数是构造函数的互补:当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。
析构函数可用于释放构造对象时或在对象的生命期中所获取的资源。
不管类是否定义了自己的析构函数,编译器都自动执行类中非 static 数据成员的析构函数。
(3) 复制控制
复制构造函数、赋值操作符和析构函数总称为复制控制。编译器自动实现这些操作,但类也可以定义自己的版本。
(4) 两种初始化形式
C++ 支持两种初始化形式:直接初始化和复制初始化。直接初始化将初始化式放在圆括号中,复制初始化使用 = 符号。
对于内置类型,例如int, double等,直接初始化和复制初始化没有区别。
对于类类型:直接初始化直接调用与实参匹配的构造函数;复制初始化先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。直接初始化比复制初始化更快。
(5)形参和返回值
当形参或返回值为类类型时,由该类的复制构造函数进行复制。
(6)初始化容器元素
复制构造函数可用于初始化顺序容器中的元素。例如:
vector svec(5);
编译器首先使用 string 默认构造函数创建一个临时值,然后使用复制构造函数将临时值复制到 svec 的每个元素。
(7)构造函数与数组元素
如果没有为类类型数组提供元素初始化式,则将用默认构造函数初始化每个元素。
如果使用常规的花括号括住的数组初始化列表来提供显式元素初始化式,则使用复制初始化来初始化每个元素。根据指定值创建适当类型的元素,然后用复制构造函数将该值复制到相应元素:
Sales_item primer_eds[] = { string(“0-201-16487-6”),
string(“0-201-54848-8”),
string(“0-201-82470-1”),
Sales_item()
};

1.2 合成的复制构造函数
(1)合成的复制构造函数
如果没有定义复制构造函数,编译器就会为我们合成一个。
合成复制构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本。
逐个成员初始化:合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。
例外:如果一个类具有数组成员,则合成复制构造函数将复制数组。复制数组时合成复制构造函数将复制数组的每一个元素。

1.3 定义自己的复制构造函数
(1) 只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义复制构造函数,也可以复制。
class Peopel
{
public:
std::string name;
unsigned int id;
unsigned int age;
std::string address;
};
(2) 有些类必须对复制对象时发生的事情加以控制。
例如,类有一个数据成员是指针,或者有成员表示在构造函数中分配的其他资源。而另一些类在创建新对象时必须做一些特定工作。这两种情况下,都必须定义自己的复制构造函数。
最好显式或隐式定义默认构造函数和复制构造函数。如果定义了复制构造函数,必须定义默认构造函数。

1.4 禁止复制
有些类需要完全禁止复制。例如,iostream 类就不允许复制。延伸:容器内元素不能为iostream
为了防止复制,类必须显式声明其复制构造函数为 private。

2 赋值操作符
与复制构造函数一样,如果类没有定义自己的赋值操作符,则编译器会合成一个。
(1)重载赋值操作符
Sales_item& operator=(const Sales_item &);
(2)合成赋值操作符
合成赋值操作符会逐个成员赋值:右操作数对象的每个成员赋值给左操作数对象的对应成员。除数组之外,每个成员用所属类型的常规方式进行赋值。对于数组,给每个数组元素赋值。
(3)复制和赋值常一起使用
一般而言,如果类需要复制构造函数,它也会需要赋值操作符。

3 析构函数
构造函数的用途之一是自动获取资源;与之相对的是,析构函数的用途之一是回收资源。除此之外,析构函数可以执行任意类设计者希望在该类对象的使用完毕之后执行的操作。
(1) 何时调用析构函数
撤销(销毁)类对象时会自动调用析构函数。
变量(类对象)在超出作用域时应该自动撤销(销毁)。
动态分配的对象(new A)只有在指向该对象的指针被删除时才撤销(销毁)。
撤销(销毁)一个容器(不管是标准库容器还是内置数组)时,也会运行容器中的类类型元素的析构函数(容器中的元素总是从后往前撤销)。
(2)何时编写显式析构函数
如果类需要定义析构函数,则它也需要定义赋值操作符和复制构造函数,这个规则常称为三法则:如果类需要析构函数,则需要所有这三个复制控制成员。
(3)合成析构函数
合成析构函数按对象创建时的逆序撤销每个非 static 成员,因此,它按成员在类中声明次序的逆序撤销成员。
对于每个类类型的成员,合成析构函数调用该成员的析构函数来撤销对象。
合成析构函数并不删除指针成员所指向的对象。 所以,如果有指针成员,一定要定义自己的析构函数来删除指针。

 *析构函数与复制构造函数或赋值操作符之间的一个重要区别:即使我们编写了自己的析构函数,合成析构函数仍然运行*。

四 友元
友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类。
友元可以出现在类定义的内部的任何地方。
友元不是授予友元关系的那个类的成员,所以它们不受声明出现部分的访问控制影响。
建议:将友元声明成组地放在类定义的开始或结尾。

1 友元类
class Husband
{
public:
friend class Wife;
private:
double money;//钱是老公私有的,别人不能动,但老婆除外
};

class Wife
{
public:
void Consume(Husband& h)
{
h.money -= 10000;//老婆可以花老公的钱
}
};

Husband h;
Wife w;
w.Consume(h);

2 使其他类的成员函数成为友元
class Husband; //1.声明Husband

class Wife //2.定义Wife类
{
public:
void Consume(Husband& h);
};

class Husband //3.定义Husband类
{
public:
friend void Wife::Consume(Husband& h);//声明Consume函数。
private:
double money;//钱是老公私有的,别人不能动,但老婆除外
};

void Wife::Consume(Husband& h) //4.定义Consume函数。
{
h.money -= 10000;//老婆可以花老公的钱
}
注意类和函数的声明和定义的顺序:

(1)声明类Husband 
(2)定义类Wife,声明Consume函数
(3)定义类Husband

五 static 类成员

static 成员,有全局对象的作用,但又不破坏封装。
1 static 成员变量
static 数据成员是与类关联的对象,并不与该类的对象相关联。
static 成员遵循正常的公有/私有访问规则。

2 使用 static 成员而不是全局对象有三个优点。
(1) static 成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。
(2) 可以实施封装。static 成员可以是私有成员,而全局对象不可以。
(3) 通过阅读程序容易看出 static 成员是与特定类关联的,这种可见性可清晰地显示程序员的意图。

3 static 成员函数
在类的内部声明函数时需要添加static关键字,但是在类外部定义函数时就不需要了。
因为static 成员是类的组成部分但不是任何对象的组成部分,所以有以下几个特点:
1) static 函数没有 this 指针
2) static 成员函数不能被声明为 const (将成员函数声明为 const 就是承诺不会修改该函数所属的对象)
3) static 成员函数也不能被声明为虚函数

4 static 数据成员
static 数据成员可以声明为任意类型,可以是常量、引用、数组、类类型,等等。
static 数据成员必须在类定义体的外部定义(正好一次),并且应该在定义时进行初始化。
建议:定义在类的源文件中名,即与类的非内联函数的定义同一个文件中。注意,定义时也要带上类类型+”::”
double Account::interestRate = 0.035;

5 特殊的静态常量整型成员
静态常量整型数据成员可以直接在类的定义体中进行初始化,例如:
static const int period = 30;
当然char 可以转换成整形,也是可以的, static const char bkground = ‘#’;

6 其他
(1)static 数据成员的类型可以是该成员所属的类类型。非 static 成员只能是自身类对象的指针或引用
class Screen
{
public:
// …
private:
static Screen src1; // ok
Screen *src2; // ok
Screen src3; // error
};
(2)非 static 数据成员不能用作默认实参,static 数据成员可用作默认实参
class Screen
{
public:
Screen& clear(char = bkground);
private:
static const char bkground = ‘#’;//static const整形变量可以在类内部初始化。
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值