美文网首页
004-C++基础笔记

004-C++基础笔记

作者: 千转军师 | 来源:发表于2021-03-15 16:12 被阅读0次

    时间:2021年3月15日16:13:02

    参考《Primer C++第五版》

    一、基础

    1.1 基本变量

    (1)算术类型变量

    • bool
    • char
    • wchar_t
    • char16_t
    • char32_t
    • short
    • int
    • long
    • long long
    • float
    • double
    • long double

    (2)复合类型

    • 指针
    int val = 100;
    int *val_p = val;
    
    • 引用
    int val = 100;
    int &val_ref = val;
    
    • 引用和指针的不同
      a、不存在空引用。引用必须连接到一块合法的内存;
      b、一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象;
      c、 引用必须在创建时被初始化。指针可以在任何时间被初始化。

    1.2 关键字

    参考:
    https://www.runoob.com/w3cnote/cpp-keyword-intro.html

    asm else    new this
    auto    enum    operator    throw
    bool    explicit    private true
    break   export  protected   try
    case    extern  public  typedef
    catch   false   register    typeid
    char    float   reinterpret_cast    typename
    class   for return  union
    const   friend  short   unsigned
    const_cast  goto    signed  using
    continue    if  sizeof  virtual
    default inline  static  void
    delete  int static_cast volatile
    do  long    struct  wchar_t
    double  mutable switch  while
    dynamic_cast    namespace   template
    

    1.3 const关键字

    const int a = 10;
    

    默认情况下const修饰的变量仅在本文件中有效
    (1)const的引用
    又时称为对常量的引用

    const int a = 10;
    const int &re = a;
    

    也可以引用const变量

    int a = 10;
    const int &re = a;
    

    (2)常量指针

    • 指向常量的指针,称之为常量指针;
    • 常量的地址只能赋值给常量指针;
    • 常量指针指向的数值不能更改。
    int a = 10;
    const int *p = a;
    

    (3)顶层和底层const
    在使用const来修饰指针的时候,可以修饰指针本身,也可以修饰指针指向的变量,前者为顶层const,后者为底层const:

    const int a = 10;
    const int *p1 = a;  //修饰该指针指向的变量为不可变的
    int const *p2 = a;  //修饰指针为不可变的
    const int const *p3 = a;  //修饰了指针和指针所指向的变量
    

    1.4 别名

    typedef
    (1)指针别名

    typedef char *type_pointer;   //type_pointer代表的是char *
    

    1.5 自定义的数据结构

    类(class)、结构体(struct)
    C++库提供的类,例如 std::String

    1.6 一个例子

    #include <iostream>
    #include <string>
    using namespace std;
    int main()
    {
        string str("LL");
        str.append(" OK");
        cout << str;
        return 0;
    }
    

    1.7 命名空间

    • 关键字 using。
    • 常用的命名空间 std,使用
    using std;
    using pos = std::string::size_type;     //明确值使用某个部分
    

    如果没有声明命名空间,则可以像std::cout 和 std::cin 显式地调用,其中 :: 为作用域。

    • 头文件一般不包含 using,以防止重复包含。

    1.8 标准库的类string

    //string调用前提
    #include <string>
    using std::string
    

    (1)拷贝初始化和直接初始化

    string str = "OK";  //  拷贝初始化
    string str2("GG");  //直接初始化
    

    (2)常用操作

    • os<<s 和 is>>s :字节流
    • getline(is, s) : 从is中读取一行给s
    • s.empty : 是否为空的判断
    • s.size :字节个数获取
    • s[n] :第n个字符获取
    • s1+s2 :字符串连接
    • s1 = s2 : 字符串赋值
    • s1 == s2 :字符串比较
      类似的还有 != ,<,<=,>,>=

    (3)其他操作
    用for处理每一个字符

    for (declaration: expression)
        statement
    

    expression表示包含一个序列的对象,declaration用于定义一个变量,该变量会一次表示为序列里的一个个成员,最终完成循环。
    (4)例子:转换为大写字母

    #include <iostream>
    #include <cctype>
    #include <string>
    using namespace std;
    int main(void)
    {
        string s("ok, come for me!");
        for(auto &c : s)
          c = toupper(c);
        cout << s << endl;
        return 0;
    }
    

    1.8 头文件

    形如如cctype头文件,和C语言的 ctype.h 是一样的,但是更符合c++的风格。通常name.h 去掉 .h 然后在前面加上 c来替换。

    1.9 标准库类型vector

    • C++既有类模板,也有函数模板,vector又称容器,属于类模板;
    • vector是指同一类型对象的集合;
    • 根据模板来创建类或者函数,这个过程成为实例化;
    • 因为“引用”不是对象,所以不存在引用的vector

    (1)vector定义和初始化

    vector<T> v1
    vector<T> v2(v1)
    vector<T> v2 = v1
    vector<T> v3(n, val)
    vector<T> v4(n)
    vector<T> v5{a, b, c...}
    vector<T> v6={a, b, c...}
    

    (2)操作
    添加:push_back方法

    #include <iostream>
    #include <vector>
    using namespace std;
    int main(void)
    {
        vector<int> v;
        v.push_back(10);
        cout << v[0] << endl;
        return 0;
    }
    

    注:如果循环体包含有向vector对象添加元素的语句,则不能使用范围for循环
    操作列表:

    • v.empty
    • v.size
    • v.push_back(t)
    • v(n)
    • v1 = v2;
    • v1 = {a,b,c...}
    • v1 == v2
    • v1 != v2
    • <、<=、>、>=

    1.10 迭代器(iterator)

    (1)例子:

    string s("ok, i am here");
    if(s.begin() != s.end())
    {
        auto it = s.begin();
        *it = toupper(*it);
    }
    
    vector<int>::interator it;
    string::interator it2;
    

    迭代器有点类似于指针,假若 it 迭代器指向的是由字符串(string)组成的vector对象,那么

    (*it).empty()  // 调用string这个类的empty方法,正确
    *it.empty()  //  调用it 迭代器的empty方法,由于it 迭代器没有empty方法,所以出错
    

    注意:但凡使用的迭代器的循环体,都不要向迭代器所属容器添加元素(例如 push_back 方法)
    (2)操作

    - iter + n
    - iter - n
    - iter1 += n
    - iter1 -= n
    - iter1- iter2
    - >、>=、<、<=
    

    (3)有序序列实现二分搜索的例子

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    int main(void)
    {
        vector<string> vec_str{ "are", "luck", "just", "so so", "why","you"};
        auto beg = vec_str.begin(), end = vec_str.end();
        auto mid = vec_str.begin() + (end - beg)/2;
        string target("so so");
        while(mid != end && *mid != target)
        {
            if(target < *mid)
            {
                end = mid;
            }
            else
            {
                beg = mid + 1;
            }
            mid = beg + (end - beg)/2;  
        }
        cout << *mid << endl;
        return 0;
    }
    

    1.11 数组

    • 数组类似于容器 vector,是一类相同数据类型对象的集合。但是数组在定义后,其个数不能更改,所以缺乏一定的灵活性。
    • 数组的元素为对象,不能是引用,所以没有引用的数组。
    • 数组定义时,个数可以是变量(老标准不支持),例如
    int a = 10;
    int b[a];
    

    (1)初始化例子

    int a[3] = {1, 2,};
    int a2[3] = {1, 2};
    int a3[] = {1, 2,};
    int a4[] = {1, 2};
    char str[] = "good job";
    

    注:不可以拷贝或者赋值,例如

    int a[10], b[10];
    a = b;  //错误
    

    1.12 略去部分

    • 表达式和运算符
    • 条件语句、迭代语句、跳转语句

    1.13 try语句和异常处理

    异常处理机制包括异常检测和异常处理,而c++的异常处理包括了:

    • throw表达式:在异常检测中发出,表明遇到了异常;
    • try语句块:对异常进行处理,通常有多个 catch子句;
    • 一套异常的类,用于在throw表达式和catch子句之间传递异常的具体信息。

    (1)模式

    try{
      [代码块]
    }catch(<异常的标识符>){
      [异常处理代码]
    }catch(<异常的标识符>){
     [异常处理代码]
    }
    

    (2)标准异常
    头文件 stdexcept 包含的异常类

    • exception:常见错误
    • runtime_error
    • range_error
    • overflow_error
    • underflow_error
    • logic_error:程序逻辑错误
    • domain_error
    • invalid_argument
    • length_error
    • out_of_range
      (3)例子
    #include <iostream>
    #include <stdexcept>
    using namespace std;
    int main(void)
    {
        try{
            if(1)
                throw runtime_error("runtime error is comming!");
        }catch(runtime_error){
            cout << "catch a except case";
        }
        
        return 0;
    }
    

    1.14 重载函数

    • 在同一作用域内的几个函数,其函数名相同但是形参列表不同,这类函数成为重载函数。
    • main函数不能重载。
    • 含有顶层const的形参无法与不含顶层const的形参区分。

    二、 类

    • 面向对象的思想;
    • 一种自定义的数据类型;
    • 考虑对象的拷贝、移动、赋值和销毁;
    • 类的基本思想:数据抽象、数据封装;
    • 数据抽象是依赖于接口和实现相分离的编程技术;
    • 类的接口:用户所能执行的操作;
    • 类的实现:类成员、负责接口实现的函数、其他私有函数

    2.1 类成员函数

    • 定义在类内部的而函数是隐式的inline函数。
    • 类成员函数直接在类内部定义,也可以只在类内部声明,而在类外部定义;
    #include <iostream>
    #include <string>
    using namespace std;
    class book{
    public:
        string name;
        float price;
        string get_name(void){return name;}
        float get_price(void);
    };
    float book::get_price(void)
    {
        return price;
    }
    int main(void)
    {
        book b;
        b.name.append("Three Body");
        b.price = 25.9;
        cout << "name:" << b.get_name() << "  price:"<< b.get_price() <<endl; 
        
        return 0;
    }
    

    2.2 构造函数

    • 构造函数没有返回类型;
    • 构造函数不能声明为const的;
    • 类通过默认构造函数来实现默认初始化;
    #include <iostream>
    #include <string>
    //#include <vector>
    //#include <stdexcept>
    using namespace std;
    class book{
    public:
        book(string n);
        string name;
        float price;
    };
    book::book(string n)
    {
        name = n;
    }
    int main(void)
    {
        book b("Three Body");
        cout << "name:" << b.name << endl; 
        
        return 0;
    }
    

    2.3 类的拷贝、赋值和析构

    编译器有默认的对类的拷贝、赋值和销毁操作,但是有些类的合成版本,会无法工作(特别是分配类对象之外的资源时,合成版本常常失败)。

    2.4 访问控制和封装

    使用struct和class定义类的唯一区别是默认的访问权限不同。
    访问说明符:

    • public
    • private
      私有类型,限制非类成员访问。

    2.5 友元

    • 令其他类(A)或者函数(F)成为某个类(B)的友元类或函数,那么A类或者F函数就可以访问B类的非公有成员了;
    • 关键词: friend;
    • 友元声明可以在类定义中的任意位置,不受访问限制区域的影响。
      例子:
    #include <iostream>
    #include <string>
    using namespace std;
    class book{
        friend void show(book);
    public:
        book(string n);
    private:
        string name;
        float price;
    };
    book::book(string n)
    {
        name = n;
    }
    void show(book b)
    {
        cout << "name:" << b.name << endl;
    } 
    int main(void)
    {
        book b("Three Body");
        show(b);
        
        return 0;
    }
    

    2.6 内联成员函数

    在类外定义函数成员,前面加上 inline关键词

    2.7 类的静态成员

    • 类的静态成员变量,和类有关联,不像其他变量,只和类的对象有关;
    • 静态成员不属于任何一个类的对象,但是可以通过类的作用域来访问静态成员变量,也可以通过某个类对象、引用、指针来访问,例如:
    #include <iostream>
    using namespace std;
    class book{
    public:
        static int code;
        string name;
        float price;
    };
    void show(book b)
    {
        cout << "code:" << b.code << endl;
    } 
    int book::code = 0;
    int main(void)
    {
        book b1, b2;
        book::code = 10;
        show(b1);
        show(b2);
        return 0;
    }
    

    2.8 函数后面的冒号

    例如

    class cc{
        cc(int);
        int a;  
    }
    cc::cc(int b):a(b)
    {
        ;
    }
    

    起到赋值的作用,相当于给成员 a赋值为b


    C++标准库

    • IO库
    • 顺序容器
    • 泛型算法
    • 关联容器
    • 动态内存

    三、IO库

    • istream:(输入流)类型,提供输入操作
    • ostream:(输出流)类型,提供输入操作
    • cin:一个istream对象,从标准输入读取数据
    • cout:一个ostream对象,向标准输出写入数据
    • cerr:一个ostream对象,向标准输出写入数据
    • >>:运算符,从istream对象中读取数据
    • <<:运算符,向一个ostream对象中写入数据
    • getline:从一个istream对象中读取数据,并存入string对象中

    注:

    • io对象无拷贝和赋值

    3.1 io库的类和头文件

    (1)头文件 iostream

    • istream,wistream 从流中读取数据
    • ostream,wostream 向流写入数据
    • iostream,wiostram 读写流

    (2)头文件 fstream

    • ifstream,wifstream 从文件中读取数据
    • ofstream,wofstream 向文件中写入数据
    • fstream, wfstream 读写文件

    (3)头文件 sstream

    • istringstream, wstringstream 从string中读取数据
    • ostringstream, wostringstream 向string写入数据
    • stringstream, wstringstream 读写string

    (4)条件状态
    例如

    int a;
    cin >> a;
    

    如果输入的不是数字,那么返回的错误的状态

    while(cin >> a);
    

    3.2 输出缓冲

    cout << unitbuff;   //所有输出操作后都会立即刷新缓存区
    cout << nounitbuf;  //回到正常的缓冲方式
    

    3.3 文件的输入输出

    文件操作

    • fstream fstrm 创建一个未绑定的文件流;
    • fstream fstrm(s) 创建一个文件流,并打开名为s的文件(s可以是字符串,也可以是string类的对象);
    • fstream fstrm(s, mode) 创建一个文件流,并且以某种模式打开s文件;
    • fstrm.open(s) 打开s文件;
    • fstrm.close() 关闭文件流;
    • fstrm.is_open() 是否打开文件的判断。

    文件打开的模式

    • in 以读的方式打开
    • out 以写的方式打开
    • app 每次操作均定位到文件末尾
    • ate 打开文件后立即定位到文件末尾
    • trunc 截断文件
    • binary 以二进制形式进行输入输出操作
      使用例子:
    #include <fstream>
    using namespace std;
    int main(void)
    {
        fstream fs("aa.txt", ifstream::out);
        fs << "OK";
        return 0;
    }
    

    四、顺序容器

    类型别名

    • iterator 此容器的迭代器类型
    • const_interator 可读取元素但是不能修改元素
    • size_type 类型的大小
    • difference_type
    • value_type 元素类型
    • reference 元素的左值类型,与 value_type& 含义相同
    • const_reference 元素的const左值类型

    容器定义和初始化
    C c
    C c1(c2)
    C c1 = c2
    C c{a,b,c...}
    C c={a,b,c...}
    C c(b,e)
    C seq(n)
    C seq(n,t)

    赋值和交换
    c1 = c2
    c={a,b,c...}
    swap(c1,c2)
    c1.swap(c2)
    seq.assign(b,e)
    seq.assign(il)
    seq.assign(n,t)

    4.1 顺序容器的类别

    • vector 可变大小的数组
    • deque 双端队列
    • list 双向链表
    • forward 单向链表
    • array 固定大小数组
    • string 与vector相似,专用于保存字符

    特点:

    • vector、array和string都是在连续的空间里进行存储的,所以随机地通过下标来访问速度很快;但在插入删除时候,需要移动后面的元素,所以较慢。
    • list和forward是随机存储,访问时要遍历整个链表,比较耗时;但是插入和删除操作比较迅速。
    • deque 和vector类似,是连续存储;插入和删除动作,如果是在元素中间,那么代价和vector一样,但是如果在两侧的话,又变得和list一样快速了。
    • array是比内置数组更为安全、易于使用的数组类型。

    4.2 容器操作

    4.3 迭代器

    迭代器范围: begin、end
    包含begin但是不包含end,为左闭合区间: [begin, end)

    4.4 顺序容器

    (1)添加

    • forward_list 专属的insert和emplace,不支持push_back和emplace_back
    • vector和string不支持push_front和emplace_front
    • c.push_back(t) 或者 c.push_back(args) 在c的尾部创建一个值为t的元素
    • c.emplace_back(t) 或者 c.emplace_back(args) 在c的头部创建值为t或者有args创建的元素。
    • c.insert(p,t) 或者 c.insert(p,args) 在迭代器p指向的元素之前创建一个值为t或者有args创建的元素
    • c.insert(p,n,t) 在迭代器配置项的元素之前插入n个值为t的元素,返回指向新添加的第一个元素
    • c.insert(p,il) il是一个花括号保卫的元素值列表,插入到p指向的元素之前

    emplace函数在容器汇总直接构造元素。

    (2)元素访问

    • c.back() 返回c中尾元素的引用
    • c.front() 返回c中头元素的引用
    • c[n] 返回c中下标为n的元素
    • c.at(n) 返回下标为n的元素,如果下标越界,则抛出out_of_range 的异常

    (3)元素删除

    • forward_list 有特殊的erase
    • forward_list不支持pop_back
    • vector和string 不支持pop_front
    • c.pop_back() 删除c中的尾元素
    • c.pop_front() 删除c中的首元素
    • c.erase(p) 删除迭代器p所指向的元素
    • c.erase(b,e) 删除迭代器b和e所指定范围的元素
    • c.clear() 删除c中的所有元素

    (4)容器大小更改

    • c.resize(n) 调整c的大小为n个元素
    • c.resize(n,t) 调整c的大小为n个元素,其他新增的元素都初始化为t的值

    注:如果缩小容器,那么可能导致迭代器、指针和引用失效

    (5)容器大小管理

    • shrink_to_fit 值适用于vector、string和deque
    • capacity 和reserve值适用于vector和string
    • c.shrink_to_fit() 将capacity()减少为与size()相同大小
    • c.capacity() c可以保存的元素个数
    • c.reserve(n) 分配至少能容纳元素的内容空间

    五、泛型算法

    • 提供了经典算法接口,可应用于多种不同的元素类型和容器类型;
    • 大部分在头文件algorithm 中有所定义;
    • 在标准库的numeric 头文件中定义了一组数值泛型算法
    • 迭代器令算法不依赖于容器,但是算法依赖于元素类型的操作;
    • 算法永远不会执行容器的操作
    • 标准库提供了超过100个算法(这些算法有一致的结构)

    5.1 算法

    • find
    • sort
    • accumulate
    • bind
    • replace

    5.2 lambda 表达式

    • 对于一个对象或者一个表达式,如果可以对其使用调用运算符,则称它为可调用的。例如函数和函数指针,就是可调用的对象。
    • 形式
      [capture list](parameter list) -> return type {function body}
    • 例子
      auto f = [] {return 42;};
    • 注:我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。

    例子1:

    #include <iostream>
    using namespace std;
    int main(void)
    {
        auto f = [] {return 42;};
        cout << f() << endl;
        return 0;
    }
    

    例子2

    #include <iostream>
    using namespace std;
    typedef void (*fun_p)(void);
    void fun(int mode, fun_p fp)
    {
        cout << "fun:mode=" << mode << endl;
        fp();
    }
    void fun_call(void)
    {
        cout << "fun_call is comming!" << endl;
    }
    void fun_call2(int type)
    {
        cout << "fun_call2: type=" << type << endl;
    }
    int main(void)
    {
        fun(1, fun_call);
        fun(2, [](){fun_call2(2);});
        return 0;
    }
    

    5.3 五类迭代器

    • 输入迭代器 只读,不写;单遍扫描,只能递增
    • 输出迭代器 只写,不读;单遍扫描,只能递增
    • 前项迭代器 可读写;多遍扫描,只能递增
    • 双向迭代器 可读写,多遍扫描,可递增递减
    • 随机访问迭代器 可读写,多遍扫描,支持全部迭代器运算

    5.4

    算法形参模式

    alg(beg, end, other args);
    alg(beg, end, dest, other args);
    alg(beg, end, beg2, other args);
    alg(beg, end, beg2, end2, other args);
    

    六、关联容器

    关联容器

    • map 关联数组:保存关键字-值对
    • set 关键字即值,即只保存关键字的容器
    • multimap 关键字可以重复出现的map
    • multitest 关键字可以重复出现的set

    无序集合

    • unordered_map 用哈希函数组织的map
    • unordered_set 用哈希函数组织的set
    • unordered_multimap 哈希组织的map;关键字可以重复出现
    • unordered_multitest 哈希组织的set;关键字可以重复出现

    七、动态内存

    • new :分配内存并返回指针
    • delete :销毁指针指向的对象
    • 新库提供的安全指针:shared_ptr允许多个指针指向同一个对象;unique_ptr- - 只允许一个指针指向某个对象;weak_ptr为弱指针,指向shared_ptr指向的管理对象。

    智能指针也是模板,类似于vector,所以用法如下:

    share_ptr<string> p1;    // share_ptr,可以指向string
    

    7.1 动态数组

    #include <iostream>
    using namespace std;
    int main(void)
    {
        int *p = new int[10];
        *p = 10;
        cout << *p << endl;
        return 0;
    }
    

    7.2 文本查询例子

    (来自书本例子)

    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    #include <set>
    #include <map>
    #include <fstream>
    #include <sstream>
    using namespace std;
    
    class QueryResult{
        friend std::ostream &print(std::ostream&, const QueryResult&);
        using line_no = std::vector<std::string>::size_type;
        public:
        QueryResult(std::string s,
            std::shared_ptr<std::set<line_no>> p,
            std::shared_ptr<std::vector<std::string>> f):
            sought(s), lines(p), file(f){}
        private:
            std::string sought;
            std::shared_ptr<std::set<line_no>> lines;
            std::shared_ptr<std::vector<std::string>> file;
    };
    
    class TextQuery{
    public:
        using line_no = std::vector<std::string>::size_type;
        TextQuery(std::ifstream&);
        QueryResult query(const std::string&)const;
    private:
        std::shared_ptr<std::vector<std::string>> file;
        std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
        
    };
    
    TextQuery::TextQuery(ifstream &is):file(new vector<string>)
    {
        string text;
        while(getline(is, text))
        {
            file->push_back(text);
            int n = file->size() - 1;
            istringstream line(text);
            string word;
            while(line >> word)
            {
                auto &lines = wm[word];
                if(!lines)
                {
                    lines.reset(new set<line_no>);
                }
                lines->insert(n);
            }
        }
    }
    
    QueryResult TextQuery::query(const string &sought)const
    {
        static shared_ptr<set<line_no>> nodata(new set<line_no>);
        auto loc = wm.find(sought);
        if(loc == wm.end())
        {
            return QueryResult(sought, nodata, file);       
        }
        else
        {
            return QueryResult(sought, loc->second, file);
        }
        
    }
    
    void runQueries(ifstream &infile)
    {
        TextQuery tq(infile);
        while(true)
        {
            cout << "enter word to look for, or q to quit:";
            string s;
            if(!(cin >> s) || s == "q") break;
            print(cout, tq.query(s)) << endl;
            
        }
    }
    
    ostream &print(ostream & os, const QueryResult &qr)
    {
        os << qr.sought << " occurs " << qr.lines->size() << " "
            //<< make_plural(qr.lines->size(), "time", "s") << endl;
            << " times"  << endl;
        for(auto num : *qr.lines)
        {
            os << "\t" << num + 1 << ")"
                << *(qr.file->begin() + num) << endl;
        }
        return os;
        
    }
    
    int main(void)
    {
        ifstream ifs("t.s");
        ifstream &ifs_r = ifs;
        runQueries(ifs_r);
        
        return 0;
        
    }
    

    八、类的拷贝控制

    • 拷贝、赋值和销毁

    8.1 拷贝、赋值和销毁

    特殊的成员函数

    • 拷贝构造函数
    • 移动构造函数
    • 拷贝复制运算符
    • 移动赋值运算符
    • 析构函数

    8.1 拷贝构造函数

    class Foo{
    public:
        Foo();  //默认构造函数
        Foo(cost Foo&); //拷贝构造函数
    }
    

    合成拷贝构造函数:依次将原对象中的非static成员拷贝到新创建的对象中,不同的成员,如:

    • 类类型:拷贝构造函数
    • 内置类型:直接拷贝
    • 数组:逐个元素拷贝

    8.2 拷贝赋值运算符

    重载运算符:运算符本质也是函数
    关键字:operator

    #include <iostream>
    using namespace std;
    class Foo{
    public:
        int result;
        Foo& operator=(const Foo&);
    };
    Foo& Foo::operator=(const Foo& a)
    {
        result = a.result;
        return *this;
    }
    int main(void)
    {
        Foo f1, f2;
        f1.result = 10;
        f2 = f1;
        cout << "f1:"<< f1.result << "\tf2:" << f2.result << endl;
        return 0;   
    }
    

    8.3 析构函数

    • 释放对象所占用的资源,销毁非static成员;
    • 关键符号: ~;
    • 当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数;

    8.4 使用=default

    在类的内部定义成员时,在后面加上=default,合成的函数将隐式地声明为内联的;当成员在类外定义是加上=default,那么合成的函数会当做非内联处理。

    8.5 使用=delete

    与使用=default, =delete也可以应用在 函数后面,表示不能够被使用,例如:

    class Nocopy{
        Nocopy() = defalut; //使用默认的构造函数
        Nocopy(const Nocopy&) = delete; //禁止使用拷贝构造函数
        Nocopy & operator=(const Nocopy &) = delete;    //禁止使用赋值
    };
    

    析构函数不能是删除的成员。

    8.6 私有的拷贝控制

    private 下的拷贝构造函数,普通代码无法访问

    8.7 定义行为像值的类

    例子;

    #include <iostream>
    #include <string>
    using namespace std::string;
    
    class HasPtr{
    public:
        HasPtr(cnst std::string &s = std::string()):
            ps(new std:;string(s)), i(0){}
        HasPtr(const HasPtr &p):
            ps(new std:;string(*p.ps)), i(p.i){}
        HasPtr &operator=(const HasPtr &);
    ~HasPtr(){delete ps;}
    private:    
        std:string *ps;
        int i;
    }
    HasPtr &operator=(const HasPtr &rhs)
    {
        auto newp = new string (*rhs.ps);
        delete ps;
        ps = newp;
        i =rhs.i;
        return *this;
    }
    
    int main(void)
    {
        return 0;
    }
    

    8.8 定义行为像指针的类

    增加对指针共享的计数

    #include <iostream>
    using namespace std;
    
    class HasPtr{
    public:
        HasPtr(const std::string &s = std::string()):
            ps(new std::string(s)), i(0), use(new std::size_t(1)){}
        HasPtr(const HasPtr &p):
            ps(p.ps), i(p.i), use(p.use){++*use;}
        HasPtr& operator=(const HasPtr&);
        ~HasPtr();
    private:
        std::string *ps;
        int i;
        std::size_t *use;
    };
    HasPtr::~HasPtr()
    {
        if(--*use == 0)
        {
            delete ps;
            delete use;
        }
    }
    HasPtr& HasPtr::operator=(const HasPtr &rhs)
    {
        ++*rhs.use;
        if(--*use == 0)
        {
            delete ps;
            delete use;
        }
        ps = rhs.ps;
        i = rhs.i;
        use =rhs.use;
        return *this;
    }
    int main(void)
    {
        return 0;   
    }
    

    8.9 交换操作

    自定义或者使用库的swap函数

    8.10 动态内存分配

    std::allocator 类模板是所有标准库容器所用的默认分配器 (Allocator),若不提供用户指定的分配器。

    8.11 对象移动

    九、操作重载和类型转换

    • 输入输出运算符
    • 算术和关系运算符
    • 赋值运算符
    • 下标运算符
    • 递增和递减运算符
    • 成员访问运算符
    • 函数调用运算符
    • 函数调用运算符
    • 重载、类型转换和运算符

    9.1 规则

    • 内置类的运算符不能被修改。

    • 运算符


      图片.png
    • 两种表达方式

    data1 + data2;
    operator+(data1, data2);
    
    • 重载运算符本质是一次函数调用,所以关于对象求值顺序的规则无法应用到重载运算符上。

    【待续】

    十、面向对象程序设计

    • 面向对象程序设计基于三个基本概念:数据抽象、继承和动态绑定;
    • 继承、基类、派生类
    • 基类将类型相关的函数与派生类不做改变直接继承的函数区别对待。这类函数,基类希望它的派生类各自定义适合自己的版本,此时就将这些函数声明为虚函数。
    • 虚函数关键词: virtual
    • 动态绑定:执行基类和派生类共有的函数fun,某形参A为基类类型,在实际调用时,若传给A的实参为基类,那么决定执行基类函数;若A为派生类,那么执行派生类函数。
    • 多态性:
      我们把具有继承关系的多个类型成为多态类型,因为我们能使用这些类型的”多种形式“而无需在意它们的差异。引用或者指针的静态类型与冬天类型不同是C++语言支持多态性的根本所在。

    10.1 虚函数

    • 派生类的函数继承了某个虚函数,则它的形参类型必须一致;
    • 派生类继承的虚函数返回值应该与基类虚函数的匹配,但如果返回类型为类的本身的指针或者引用是,可以忽略这个规则;

    (1)关键词 override 的使用
    由于继承类对虚函数的覆盖可能没写正确,一般编译器不报错,就会造成程序的排错很难进行。如果使用override关键词,那么就明确了继承类是要覆盖虚函数的,可以排除更多错误。

    class A{
        virtual void fun1(int) const;
        virtual void fun2();
        void fun3();
    }
    class B : A{
        void fun1(int) const override;  //与基类的fun1匹配
        void fun2(int) override;    //错误:没有匹配项
        void fun3() override;   //错误:fun3不是虚函数
        void fun4() override;   //错误:没有fun4这个虚函数    
    }
    

    (2)继承类指定调用基类的虚函数,用作用域符号 ::

    A::fun1(var);
    

    (3)抽象基类和纯虚函数

    • 含有纯虚函数的类称为抽象基类;
    • 抽象基类可用于定义接口;
    • 纯虚数的定义方法
      加上 “=0”,且只能在类的内部声明处添加,在类外部定义函数体。
    virtual void fun() = 0;
    
    • 不能直接创建一个抽象基类的对象,所以抽象类可用于定义不需要或不应该创建对象的类;
    • 重构负责重新设计类的体系,以便将操作和数据从一个类移到另一个类中。

    10.2 访问控制和继承

    #include <iostream>
    using namespace std;
    //基类-商品
    class Goods{
    public:
        double getPrice(void){return price;}
    private:
        double price;
    };
    //书籍类继承与商品类,继承方式是public
    class Book : public Goods{
    public:
        long long getIsdn(void){return isdn;}
    private:
        long long isdn;
        
    };
    int main(void)
    {
        book b;
        return 0;   
    }
    

    (1)
    public:公共的
    protect: 受保护的
    private:私有的

    (2)规则

    • 严格程度 public < protected < private;
    • 类用户不可访问保护和私有的成员;
    • 类成员和友元函数可以访问此类的其他成员,包括受保护的成员;
    • 派生类的成员或者友元可访问派生类对象中基类的保护成员;
    • 派生类的成员或者友元无法访问基类对象的保护成员;

    (3)派生访问说明符
    public、protected、private用于修饰继承,也用于修饰成员变量。

    • 派生类成员访问基类成员,只与基类成员的访问修饰符有关,而派生访问修饰符无关;其中public、protected修饰的基类成员可以被访问,private修饰的不可被访问;
    • 派生类对象直接访问基类成员,与两个修饰符都有关;如果派生类继承基类的修饰符是public,且访问的基类成员被public修饰,那么可以直接访问,其他情况不可访问。

    例子:

    //注释的代表无访问权限
    #include <iostream>
    using namespace std;
    class Base{
    public:
        int base_pub;
    protected:
        int base_pro;
    private:
        int base_pri;
    };
    class Sub1 : public Base{
    public:
        int fun_pub(void){return base_pub;}
        int fun_pro(void){return base_pro;}
        //int fun_pri(void){return base_pri;}
    };
    class Sub2 : protected Base{
    public:
        int fun_pub(void){return base_pub;}
        int fun_pro(void){return base_pro;}
        //int fun_pri(void){return base_pri;}
    };
    class Sub3 : private Base{
    public:
        int fun_pub(void){return base_pub;}
        int fun_pro(void){return base_pro;}
        //int fun_pri(void){return base_pri;}
    };
    int main(void)
    {
        int t;
        Sub1 s1;
        Sub2 s2;
        Sub3 s3;
        //派生类对象访问自己的成员函数
        t = s1.fun_pub();
        t = s1.fun_pro();
        //t = s1.fun_pri();
        t = s2.fun_pub();
        t = s2.fun_pro();
        //t = s2.fun_pri();
        t = s3.fun_pub();
        t = s3.fun_pro();
        //t = s3.fun_pri();
        //派生类对象访问基类成员
        t = s1.base_pub;
        //t = s1.base_pro;
        //t = s1.base_pri;
        //t = s2.base_pub;
        //t = s2.base_pro;
        //t = s2.base_pri;
        //t = s3.base_pub;
        //t = s3.base_pro;
        //t = s3.base_pri;
        return 0;   
    }
    

    (4)改变个别成员的访问权限
    关键词 using
    例如:

    #include <iostream>
    using namespace std;
    class Base{
    public:
        int base_pub;
    protected:
        int base_pro;
    private:
        int base_pri;
    };
    class Sub : public Base{
    public:
        using Base::base_pro;
    };
    
    int main(void)
    {
        Sub s;
        s.base_pro = 1;
        return 0;   
    }
    

    (4)默认说明符
    注: struct和class的唯一差别是默认的成员访问说明符和默认的派生访问说明符的区别。
    struct默认的派生说明符是public
    class默认的则是private

    10.3 拷贝函数和拷贝控制

    • 虚析构函数
    • 合成拷贝和继承
    • 派生类考别控制与继承
    • 继承的构造函数

    十一、模板与泛型编程

    • 定义模板
    • 模板实参推断
    • 重载和模板
    • 可变参数模板
    • 模板特例化

    11.1 模板定义

    • 模板是泛型编程的基础
    • 关键词:template、typename
    • 形式:
    template <typename T>
    

    11.2 函数模板

    例子:

    #include <iostream>
    using namespace std;
    
    template <typename T> int comp(const T a, const T b)
    {
        if(a > b)return 1;
        else return -1;
    }
    int main(void)
    {
        cout << "result:"<< comp(11.9, 20.0);
        return 0;   
    }
    

    10.3 模板类型的参数

    在模板参数列表中,typename和struct没什么不同。
    例子:

    template <unsigned N, unsigned M> 
    int comp(const char (&p1)[N], const char (&p2)[M])
    {
        return strcmp(p1, p2);
    }
    int main(void)
    {
        cout << "result:"<< comp("ok", "lpkj") << endl;
        cout << "result:"<< comp("lpkj", "ok") << endl;
        return 0;   
    }
    

    10.4 类模板

    (1)样式:

    template <typename T> class A{
      //内容
    };
    

    例子

    #include <iostream>
    #include <vector>
    using namespace std;
    template <typename T> class Foo{
    public:
        typedef T value_type;
        typedef typename std::vector<T>::size_type size_type;
        Foo(std::initializer_list<T> il){;}
        T& operator[](size_type i);
    private:
    }; 
    int main(void)
    {
        Foo<int> f = {1,2,3,4};
        return 0;   
    }
    

    (2)模板类的成员函数类外定义
    和普通的成员函数类外定义相比,多了前面的 template <typename T>

    template <typename T> class Foo::fun();
    

    (3)类型别名

    template<typename T> using twin = pair<T, T>;
    twin<string> authors;   //authors是一个 pair<string, string>
    

    10.5 可变参数模板

    #include <iostream>
    using namespace std;
    
    template <typename...Args> void g(Args ... args){
        cout << sizeof...(Args) << endl;
        cout << sizeof...(args) << endl;
    }
    int main(void)
    {
        g(1,"dfa",2.0);
        return 0;   
    }
    

    【待续】

    十一、库

    11.1 正则表达式

    即RE库,定义在regex头文件中

    十二、用在大型程序的工具

    12.1 异常处

    【。。。】

    相关文章

      网友评论

          本文标题:004-C++基础笔记

          本文链接:https://www.haomeiwen.com/subject/klikcltx.html