<<现代C++实战30讲>>打卡学习笔记—基础篇

2020-02-06  本文已影响0人  IIGEOywq

说明

基础篇9讲主要学习内存管理、容器、迭代器、异常、C++11/14/17的新语法。
每日打卡更新。

打卡Day1:堆、栈、RAII:C++里该如何管理资源?

打卡Day2:自己动手,实现C++的智能指针

#include <iostream>
using namespace std;

// template 类似于Java中的泛型
template <typename T>
class smart_ptr {
 private:
  T *ptr_;

 public:
  // explicit 构造函数关键字;
  // ptr_(ptr)表示初始化构造函数的成员变量,这个地方由于不熟悉语法,一开始没有理解。
  // C++11起 空指针用nullprt表示,古代C++用0或者NULL
  explicit smart_ptr(T *ptr = nullptr) : ptr_(ptr) {
    cout << "调用默认构造函数,分配内存;" << endl;
  }
  //析构函数(delete 对象时,才会调用)
  ~smart_ptr() {
    cout << "调用析构函数,释放内存;" << endl;
    delete ptr_;
  }
  //拷贝构造函数(通过使用另一个同类型的对象来创建一个新对象。)
  smart_ptr(smart_ptr<T> &other) {
    cout << "调用拷贝构造函数;\n";
    ptr_ = other.rlease();
  }
  //拷贝赋值(覆盖原有的对象)
  smart_ptr<T> &operator=(smart_ptr<T> &other) {
    cout << "调用拷贝赋值,赋值对象;\n";
    smart_ptr(other).swap(*this);
    return *this;
  }

  //移动构造函数
  smart_ptr(const smart_ptr &&other) { cout << "调用移动构造函数\n"; }

  smart_ptr<T> &operator=(smart_ptr<T> other) {
    other.swap(*this);
    return *this;
  }

  void swap(smart_ptr &rhs) {
    using std::swap;
    swap(ptr_, rhs.ptr_);
  }

  //将传入引用对象赋值为空,并把该对象赋值给新创建的对象
  T *rlease() {
    T *ptr = ptr_;
    ptr_ = nullptr;
    return ptr;
  }

  // const
  // 表示该方法外部不能修改,和java不同,在C++中,const可以用来声明成员函数
  T *get() const {
    cout << "对象地址:" << this << ";get==>ptr value:" << *ptr_ << endl;
    return ptr_;
  }
  // C++中的重载运算符,类似于重载现有的运算符函数。
  T *operator->() const { return ptr_; }
  T &operator*() const { return *ptr_; }
};
//共享计数
class share_count {
 public:
  share_count() : count_(1){};
  void add_count() { ++count_; }
  long reduce_count() { return --count_; }
  long get_count() const { return count_; }

 private:
  long count_;
};

// 程序的主函数
int main() {
  string value = "share_ptr";
  string *ptr = &value;
  smart_ptr<string> ptr1{
      ptr};  // C++11起可以用大括号初始化一个对象,调用默认构造函数
  // smart_ptr<int> ptr2;
  ptr1.get();
  // delete ptr1;
  smart_ptr<string> ptr3{ptr1};  //调用拷贝构造函数
  ptr3.get();
  smart_ptr<string> ptr4;
  //ptr4=ptr1; //调用拷贝赋值,编译器会报错;
  ptr4 = std::move(ptr1);  //调用移动构造函数(C++11起)
  ptr4.get();
  // 设置长度
  return 0;
}

验证输出结果:

调用默认构造函数,分配内存;
对象地址:0x7ffffffedf08;get==>ptr value:share_ptr
调用拷贝构造函数;
对象地址:0x7ffffffedf10;get==>ptr value:share_ptr
调用默认构造函数,分配内存;
调用移动构造函数
调用析构函数,释放内存;
对象地址:0x7ffffffedf18;get==>ptr value:share_ptr
调用析构函数,释放内存;

打卡Day3:右值和移动究竟解决了什么问题?

继续理解一些基础概念

// lvalues_and_rvalues2.cpp
int main()
{
    int i, j, *p;
    //  i 是 lvalue , 7 是prvalue.
    i = 7;
    // 错误使用: `j * 4` 是一个 prvalue.
    7 = i; // C2106
    j * 4 = 7; // C2106

    // *p 是 lvalue.
    *p = i;

    //  ((i < 3) ? i : j) 是左值
    ((i < 3) ? i : j) = 7;
    
    // 错误使用: ci is 不能修改的左值
    const int ci = 7;
    ci = 9; // C3892
}
class A {
  B b_;
  C c_;
};

从实际内存布局的角度,很多语言——如 Java 和 Python——会在 A 对象里放 B 和 C 的指针(虽然这些语言里本身没有指针的概念)。而 C++ 则会直接把 B 和 C 对象放在 A 的内存空间里。这种行为既是优点也是缺点。说它是优点,是因为它保证了内存访问的局域性,而局域性在现代处理器架构上是绝对具有性能优势的。说它是缺点,是因为复制对象的开销大大增加:在 Java 类语言里复制的是指针,在 C++ 里是完整的对象。这就是为什么 C++ 需要移动语义这一优化,而 Java 类语言里则根本不需要这个概念。

上面的一段直接引用。

打卡Day4:容器汇编 I:比较简单的若干容器

#include <iostream>
#include <vector>

using namespace std;

class obj1 {
 private:
  /* data */
 public:
  obj1() { cout << "obj1 构造函数\n"; };
  ~obj1() { cout << "obj1 析构函数\n"; }
  obj1(const obj1&) { cout << "obj1 拷贝构造函数\n"; }
  obj1(obj1&&) { cout << "obj1 移动构造函数\n"; }
};

class obj2 {
 private:
  /* data */
 public:
  obj2() { cout << "obj2 构造函数\n"; };
  obj2(const obj2&) { cout << "obj2 拷贝构造函数\n"; }
  // noexcept 保证提供一个不抛异常的移动构造函数
  obj2(obj2&&) noexcept { cout << "obj2 移动构造函数\n"; }
};

int main() {
  vector<obj1> v1;
  //使用reserve 分配连续内存空间
  v1.reserve(2);
  //在尾部新构造一个元素
  v1.emplace_back();
  v1.emplace_back();
  //构造第三个对象时,内存不足,分配一个新的内存,并构造第三个对象,
  //同时调用拷贝构造函数,复制第一个和第二个对象
  v1.emplace_back();
  vector<obj2> v2;
  v2.reserve(2);
  v2.emplace_back();
  v2.emplace_back();
  // 构造第三个对象时,内存不足,需要分配一个新的内存,构造第三个对象,
  //同时调用移动构造函数(因为该函数保证了抛出异常),复制第一个和第二个对象
  v2.emplace_back();
}

输出:

#第一次调用v1.emplace_back();
obj1 构造函数
#第二次调用v1.emplace_back();
obj1 构造函数
#第三次次调用v1.emplace_back();
obj1 构造函数
obj1 拷贝构造函数
obj1 拷贝构造函数
#完成后释放旧vector中的两个对象内存
obj1 析构函数
obj1 析构函数
#第一次调用v2.emplace_back();
obj2 构造函数
#第二次调用v2.emplace_back();
obj2 构造函数
#第三次调用v2.emplace_back();
obj2 构造函数
obj2 移动构造函数
obj2 移动构造函数
#完成后释放新vector中的三个对象内存
obj1 析构函数
obj1 析构函数
obj1 析构函数

打卡Day5:容器汇编 II:需要函数对象的容器

熟悉两个概念:

//less struct
template <class T>
struct less
  : binary_function<T, T, bool> {
 //重载运算符“()”
  bool operator()(const T& x,
                  const T& y) const
  {
    return x < y;
  }
};
// hash struct
template <class T> struct hash;
template <>
struct hash<int>
  : public unary_function<int, size_t> {
  size_t operator()(int v) const
    noexcept
  {
    //static_cast类型转换函数,size_t是无符号整数类型
    return static_cast<size_t>(v);
  }
};

这一讲的容器都会带上这两个函数对象

打卡Day6:异常,用还是不用,这是个问题

打卡Day7:迭代器和好用的新for循环

#include <fstream>
#include <iostream>
#include <istream>   // std::istream
#include <iterator>  // std::input_iterator_tag
#include <string>

using namespace std;

class istream_line_reader {
 public:
  class iterator {
   public:
    typedef ptrdiff_t difference_type;
    //迭代器指向的对象的值类型
    typedef std::string value_type;
    //迭代器指向的对象的指针类型
    typedef const value_type* pointer;
    //迭代器指向的对象的引用类型
    typedef const value_type& reference;
    //输入迭代器
    typedef std::input_iterator_tag iterator_category;

    iterator() noexcept : stream_(nullptr) {}
    explicit iterator(istream& is) : stream_(&is) { ++*this; }

    reference operator*() const noexcept { return line_; }
    pointer operator->() const noexcept { return &line_; }
  
    iterator& operator++() {
      getline(*stream_, line_);
      if (!*stream_) {
        stream_ = nullptr;
      }
      return *this;
    }
    iterator operator++(int) {
      iterator temp(*this);
      ++*this;
      return temp;
    }
  //迭代器相等比较
    bool operator==(const iterator& rhs) const noexcept {
      return stream_ == rhs.stream_;
    }
  //迭代器不等比较
    bool operator!=(const iterator& rhs) const noexcept {
      return !operator==(rhs);
    }

   private:
    istream* stream_;
    string line_;
  };

  istream_line_reader() noexcept : stream_(nullptr) {}
  explicit istream_line_reader(istream& is) noexcept : stream_(&is) {}
  iterator begin() { return iterator(*stream_); }
  iterator end() const noexcept { return iterator(); }

 private:
  istream* stream_;
};
int main() {
  //读取文件
  ifstream ifs{"test.txt"};
  for (const string& line : istream_line_reader(ifs)) {
    //示例循环体中仅进行简单输出
    cout << line << endl;
  }
}

打卡Day8:易用性改进 I:自动类型推断和初始化

//定义一个模版函数
template<typename T>
T max(T a, T b)
{
    // if b < a then yield a else yield b
    return b < a ? a : b;
}
//调用
int const c = 42;
max(i, c); // OK :实际调用mar(int,int)
max(c, c); // OK: 实际调用mar(int,int)
int arr[4];
max(&i, arr); // OK: 实际调用mar(int*,int*)
max(4, 7.2);    //ERROR: T 有可能是 int 或 double
std::string s;
foo("hello", s); //ERROR: T 有可能是char const[6] 或者std::string
#include <iostream>
using namespace std;
int main() {
//推导,C++17之前的写法 auto pr = make_pair(1, 42);
//这里还用到C++11的大括号初始化写法。
pair pr{1, 2}; 
}

打卡Day9:易用性改进 II:字面量、静态断言和成员函数说明符

#include <iostream>
struct length {
  double value;
  //单位
  enum unit {
    metre,
    kilometre,
    millimetre,
    centimetre,
    inch,
    foot,
    yard,
    mile,
  };
  static constexpr double factors[] = {1.0,    1000.0, 1e-3,   1e-2,
                                       0.0254, 0.3048, 0.9144, 1609.344};
  explicit length(double v, unit u = metre) { value = v * factors[u]; }
};

//重载字面量运算符(""),自定义字面量_m(注意自定义的必须以_开头)
length operator"" _m(long double v) { return length(v, length::metre); }
//自定义字面量_cm
length operator"" _cm(long double v) { return length(v, length::centimetre); }

length operator+(length lhs, length rhs) {
  return length(lhs.value + rhs.value);
}

int main() {
  length lhs = 1.0_cm;  //调用运算符 "" _m(1.0)
  length rhs = 2.0_m;   //调用运算符 "" _cm(2.0)
  length sum = lhs + rhs;
  std::cout << sum.value << std::endl;
}
#include <iostream>  //std::cout

struct A {
  int x;
  A(int x = 1) : x(x) {
    std::cout << "调用A构造函数" << std::endl;
  }  // 用户定义默认构造函数
};

struct B : A {
  // 隐式定义 B::B(),调用 A::A()
};

struct C {
  A a;
  // 隐式定义 C::C(),调用 A::A()
};

struct D : A {
  D(int y) : A(y) {}
  // 不会声明 D::D(),因为存在另一构造函数
};

struct E : A {
  E(int y) : A(y) {}
  E() = default;  // 显式预置,调用 A::A()
};

struct F {
  int& ref;     // 引用成员
  const int c;  // const 成员
                // F::F() 被隐式定义为弃置
};

int main() {
  A a;
  B b;
  C c;
  // D d; // 编译错误
  E e;
  //  F f; // 编译错误
}
class A {
public:
  virtual void foo();
  virtual void bar();
  void foobar();
};

class B : public A {
public:
  void foo() override; // OK
  void bar() override final; // OK
  //void foobar() override;
  // 非虚函数不能 override
};

class C final : public B {
public:
  void foo() override; // OK
  //void bar() override;
  // final 函数不可 override
};

class D : public C {
  // 错误:final 类不可派生
  …
};
上一篇 下一篇

猜你喜欢

热点阅读