C++ 入门指南

1.内置数据类型

1.1 字面量

整形字面量

12为10进制数、012为8进制数、0x12为16进制数、10L为长整型、10LL为longlong类型、10uL为无符号长整形、10u为无符号数、

浮点型字面量

3.14为小数、3.14e3为科学计数法小数、3.14f为单精度小数。

2.变量

2.1 定义和声明

变量只能定义一次,但可以声明多次。

int a; 变量定义。如果在函数外也就是全局变量会有默认的初始值;在函数内部也就是局部变量没有初始值(未定义值)。

int a=1; 变量定义并初始化。

extern int i; 变量声明。可以在其他文件找到这个变量。

class Hello; 类声明。

void hello(int); 函数声明。

2.2 初始化

string str; 默认初始化。调用默认初始化函数。

string str = "hello"; 赋值初始化。调用拷贝构造函数

string str("hello"); 构造函数初始化。调用对应的构造函数。

vector<int> arr{1,2,3}; 列表初始化。用于容器和数组。

2.3 引用和指针

int &b = a; b为引用。

int *b = &a; b为指针。 

引用必须初始化而且不能被修改;初始化不能是字面量,因为引用是变量的别名。

指针不用立即初始化而且可以被修改;也就是可以指向其他的对象。

int *p[3]; 为int *的数组。

int (*p)[3]; 为指向int [3]的指针。

初始化空指针

int *b=0; int *b=NULL; int *b=nullptr;

空类型指针

void *pv; 可以指向任何类型的对象。

2.4 const限定符

const int p=1; 为int常量。不能修改p的值而且p必须初始化。

const int *p;为指向常量int的指针。p不必初始化,但p指向的对象不能被修改。

int *const p; 为指向int的常量指针。p必须初始化而且p的值不能被修改。但p指向的对象可以被修改。

const int *constp; 为指向常量int的常量指针。p必须初始化而且p的值和p指向的值不能被修改。

常量指针不能赋值给非常量指针。非常量指针可以赋值给常量指针。

常量可以赋值给非常量(值传递)。非常量可以赋值给常量。

2.5 constexpr 变量

const变量的初始值并不一定是常量表达式。也就是说用这个表达式声明其他常量会与上一次声明的常量的值不一样。声明constexpr类型可以帮助编译器来验证变量的值是否为常量表达式。

constexpr int a = 20; // 20是常量表达式

constexpr int a= a + 2; // a + 2是常量表达式。

函数体内的变量不能声明为constexpr。因为是局部变量,地址不固定。

constexpr int *p; 为指向int的常量指针并不是指向常量int。

2.6 类型别名

typedef int a; a为int的别名。

using a = int; a为int的别名。

2.7 auto和decltype

auto类型会根据初始化值来判断变量类型。

decltype 会根据值来得到值的类型。

auto a = 10;  a的类型为int。

decltype(1+1) a; a的类型为int。

auto会忽略const修饰符;只获取初始化值的类型。

const int i= 1;

auto int a = i; a的类型为int而不是const int。

decltype不会忽略const和&修饰符。

const int i=1, *p=&i;

decltype(i) a= 2; a的类型是const int

decltype(*p) a=i; a的类型是int &引用。

2.8 自定义数据结构

struct Hello {

 int a; // public

} aa, *pp;

class Hello {

 int a; // private

}aa, *pp;

声明struct和class后面必须有分号;

struct和class唯一的区别是struct默认是public成员,class默认是private成员。

3. 字符串、向量和数组

3.1 字符串基本操作

3.1.1 字符串基本方法

str.empty() str为空返回true,非空返回false

str.size() 返回字符个数

str[n] 返回第n个字符的引用.

str1+str2 拼接字符串

str1=str2 拷贝字符串

==、!=、>、>=、<、<= 比较字符串

3.1.2 字符处理

cctype头文件的定义了处理字符的函数。

isalnum(ch) 判断字符ch是否为字母或者数字

isalpha(ch)  判断字符ch是否为字母

iscntrl(ch) 判断字符ch是否为控制字母

isdigit(ch) 判断字符ch是否为数字

isgraph(ch) 判断字符ch是否可以打印且不是空格

islower(ch) 判断字符ch是否是小写字母。

isprint(ch) 判断字符ch是否可以打印

isspace(ch) 判断字符ch是否为空白(空格、回车、换行等)

isupper(ch) 判断字符ch是否为大写字母

isxdigit(ch) 判断字符ch是否为十六进制字符

tolower(ch) 转换为小写字母

toupper(ch) 转换为大写字母

3.1.3 处理字符串的字符

for(auto &ch : str) {}

ch为char &类型,可以直接修改str的字符。

3.1.4 字符串构造

string s1;                // 空字符串
string s1 = "123";        // 拷贝构造
string s1("123");         // 拷贝构造
string s1(chs, n);        // 拷贝字符数组前n个字符
string s1(s2, pos);       // 拷贝pos位置之后的字符串
string s1(s2, pos, len);  // 拷贝pos位置之后len长度的字符串。  

3.1.5 字符串操作

s.substr(pos)                 // 获取子字符串
s.substr(pos,len)
s.insert(pos,args)            // 插入字符串
s.assign(args);               // 字符串赋值
s.append(args);               // 追加字符串
s.replace(pos,len,args);      // 替换range范围内字符串
s.erase(pos,len);             // 删除字符串
s.find(args);                 // 从头部查找   
s.rfind(args);                // 从尾部查找
s.find_frist_of(args);        // 从头部查找args的任意一个字符
s.find_last_of(args);         // 从尾部查找args的任意一个字符
s.find_frist_not_of(args);
s.find_last_not_of(args);
s1.compare(s2)                // 比较字符串
to_string(i)                  // 数字转字符串
stod(s)                       // 字符串转浮点型
stoi(s)
stoul(s)
stoll(s)
stof(s)

pos为下标或者迭代器
args一下形式
str            // 字符串
str,pos,len    // 子字符串
cp,len         // 子字符数组
cp             // 字符数组
n,c            // 那n个元素的字符,find不支持
b,e            // 另一个字符串的迭代器范围,find不支持
初始化列表

3.2 向量vector的基本操作

3.2.1 定义和初始化vector

vector<int > v1; 初始化空vector

vector<int> v1 = v2; 拷贝vector

vector<int> v1(v2); 拷贝构造vector

vector<int> v1(n, val); n个重复的val值

vector<int> v1(n); n个默认值

vector<int> v1{1,2,3}; 列表初始化

vector<int> v1 = {1,2,3}; 列表初始化赋值

vertor<int> v1(biter,eiter); 使用另一个数组或者容器的迭代器初始化(类型int必须一致)

3.2.2 vector基本方法

v.empty(); 是否包含元素

v.size(); 返回元素个数

v.push_back(val); 添加元素

v[n]; 返回位置为n的引用。

==、!=、>、>=、<、<= 向量比较

3.2.3 迭代器

auto a = v.begin();  指向第一个元素的迭代器

auto a = begin(v); 指向第一个元素的迭代器

auth b = v.end(); 指向最后一个元素的下一个元素(尾后)的迭代器

auth b = end(v);

*iter; 迭代器指向元素的引用。

iter->men; 相当于(*iter).men;

++iter; 指向下一个元素

--iter; 指向上一个元素

iter + n; 向前移动n个元素

iter - n; 向后移动n个元素

iter += n;

iter -= n;

iter1 - iter2; iter2向前移动到iter1的元素个数

iter1 == iter2 迭代器是否相等。

iter1 != iter2 

>、>=、<、<=

3.3 数组

3.3.1 数组定义和初始化

int a[] = {1,2,3}

int a[2];

int a[5] = {1,2}

int a[3][4][5] = {1,2,3};

3.2.2 数组和指针

数组名为指向第一个元素的指针。数组不能被拷贝

int *a = arr; 正确。

int a[] = arr; 错误。

int *a[5] 为指向int的指针数组

int (*a)[5] 为指向int[5]的指针

4. 类型转换

4.1 static_cast

只能进行类型兼容的转换。

算术的类型转换。

double val = static_cast<double>(j) / i;

空类型指针转换。

void *p;

double *pd = static_cast<double*>(p);

类型不兼容转换

int a=1;

string b = static_cast<string>(a); 错误

4.2 const_cast

常量转换为非常量。不能转换类型。

const char *pc;

char *p = const_cast<char *>(pc);

4.3 reinterpret_cast

可以转换类型不同的指针

int *i;

int string *p = reinterpret_cast<string *>(i)

5. 函数

5.1 函数声明

int hello(int,int);

5.2 形参

值传递。不影响实参。

hello(int a);

引用传递。影响实参。

void hello(int &a);

指针传递。影响实参。

void hello(int *a);

如果不改变实参,尽量使用常量引用。

void hello(const string &str)

5.3 静态局部变量

静态局部变量定义后就不会重复初始化了。直到程序结束才释放。

5.3 返回值

函数内部的局部变量的引用和指针和引用不能被返回。但是可以返回值(值传递)。

可以使用尾置返回类型。需要用auto放到原来返回值的位置。

auto bb(int a) -> int
{
  return 1;
}

5.4 函数重载

函数名相同,形参列表不同的函数。成为函数重载。

void hello();
void hello(int i);
void hello(string a, int i);

注意:函数重载不区分const

void hello(int a);
void hello(const int a); // 重复定义

5.5 默认参数

void hello(int a=1, int b=2, string c ="hello");
hello();
hello(1);
hello(1,2);
hello(1,2,"world");

5.6 内联函数

内联函数在调用点可以直接展开。

inline void hello() {
    cout << "hello world" << endl;
}

调用hello();最终会被cout<< "hello world" << endl替换。

5.7 函数指针

int (*pf)(int a, int b); // pf为函数指针
int *pf(int a, int b); // pf为返回值是int指针的函数
void fun(int a, int b, void pf(int a, int b)); // 函数指针作为形参

5.8 initializer_list

void hello(initializer_list<string> il)
{
  for (auto &value : il)
  {
    cout << value << endl;
  }
}
hello({"hello", "wolrd"});

6 类

类有数据成员和成员函数。

6.1 构造函数

6.1.1 默认构造函数

编译器会自动生成默认的构造函数。可以使用=default表示使用默认生成的构造函数。

class Hello
{
   public: 
     Hello() = default;
};

6.1.2 构造函数初始化列表

初始化列表的初始化顺序按照定义的顺序。

class Hello
{
  public:
    int a;
    int b;
    Hello(int aVal, int bVal): a(aVal), b(bVal) {} 
};

6.1.3 类外部定义构造函数

class Hello
{
   public:
     int a;
     Hello(int aVal);
};

Hello::Hello(int aVal): a(aVal) {}

6.1.4 类内初始值以及构造函数使用默认值

class Hello
{
  public:
    int a=1;
    int b=1;
    Hello(int aVal, int bVal=10): a(aVal),b(bVal) {}
};

6.1.5 委托构造函数

class Hello
{
    public:
      int a;
      int b;
      Hello(int aVal, int bVal): a(aVal), b(bVal) {}
      Hello(): Hello(10, 20) {}
};

6.1.6 拷贝构造函数

系统默认会合成拷贝构造函数,拷贝每一个非static成员。拷贝赋值,非引用类型的函数入参和函数返回值都会进行拷贝初始化。

class Hello
{
  public:
    Hello(const Hello &he);  // 拷贝构造函数
};

6.1.7 赋值运算符

class Hello
{
  public:
    Hello(int aVal): a(aVal) {};
    Hello& operator=(const Hello &he)
    {
      a = he.a;
      return *this;
    };
  private:
    int a;
};

6.1.8 析构函数

在实例销毁前执行清除操作,也会销毁实例所用的资源。如果定义了析构函数,就必须定义拷贝构造函数和赋值运算符。

class Hello
{
  public:
    ~Hello();
};

6.1.9 阻止拷贝行为

使用=delete告诉编译器删除这个函数。析构函数不能被delete

class Hello
{
  public:
    Hello(Hello &he) = delete;
};

6.1.10 定义行为像值的类

赋值操作符必须先拷贝新值再删除旧值。否则,当新值和旧值相同,删除旧值,新值也被删除。新值先拷贝不会影响到旧值的删除。

class Hello
{
  public:
    Hello(const string &s = string()): ps(new string(s)), a(0) {};
    Hello(const Hello &he): a(he.a), ps(new string(*he.ps)){};
    Hello& operator=(Hello &he)
    {
      a = he.a;
      string *newPs = new string(*he.ps);
      delete ps;
      ps = newPs;
      return *this;
    };
    ~Hello()
    {
      delete ps;
    }
  private:
    int a;
    string *ps;
};

6.1.11 定义行为像指针的类

副本和原实例将共享动态内存。使用引用计数类管理共享的动态内存。

class Hello
{
  public:
    Hello(const string &s = string()): ps(new string(s)), a(0), use(new int(1)) {};
    Hello(const Hello &he): a(he.a), ps(he.ps), use(he.use) {
      ++*use; // 引用计数加1
    };
    Hello& operator=(Hello &he)
    {
      a = he.a;
      ++*he.use;
      if (--*use == 0)
      {
        delete ps;
        delete use; 
      }
      ps = he.ps;
      use = he.use;
      return *this;
    };
    ~Hello()
    {
     if (--*use == 0) // 引用计数为0
     {
       delete ps;
       delete use;
     }
    }
  private:
    int a;
    string *ps;
    int *use;
};

6.1.12 交换操作

class HasPtr
{
  friend void swap(HasPtr&, HasPtr&);
  public:
    HasPtr(int numVal): num(numVal) {}
    inline int getNum() {return num;}
  private:
    int num = 0;
};

void swap(HasPtr &lhs, HasPtr &rhs)
{
  using std::swap;
  swap(lhs.num, rhs.num);
}

6.1.13 对象移动

在某些情况下,对象拷贝后会立即销毁,使用移动而非拷贝对象会大幅度提升性能。对象移动后一般会立即销毁,所以移动后的对象必须能正常析构。

右值引用

使用&&来获取右值引用。右值只绑定一个将要销毁的对象,因此可以将右值引用的资源移动到另一个对象。右值引用通常是要求转换的表达式、字面常量或者返回右值的表达式。左值为对象,右值为对象的值。

6.1.14 移动构造函数

移动构造函数不应抛出异常。因为移动构造函数不分配内存直接修改值,抛出异常后不能被还原。需要使用noexcept告诉编译器不会抛出异常,否则编译器认为会和标准库不兼容。例如标准库的vector需要确保push_back发送异常后,自身不会发生变化。如果没有noexcept的保证,vector也不能确保push_back功能正常。为了让标准库正常工作,移动构造函数需要使用noexcept向编译器保证不会抛出异常。

class Hello
{
  public:
    Hello(int aVal): a(aVal) {}
    Hello(Hello &&he) noexcept
    {
      a = he.a;
    }
    inline int getA() {return a;}
  private:
    int a;
};

6.1.15 移动赋值运算符

移动赋值运算符也需要标记noexcept。

class Hello
{
  public:
    Hello(int aVal): a(aVal) {}
    Hello& operator=(Hello &&he) noexcept
    {
      a = he.a;
      return *this;
    }
    inline int getA() {return a;}
  private:
    int a;
};

6.1.16 移动迭代器

allocator<int> my_alloc;
auto p = my_alloc.allocate(4);
vector<int> numbers = {1,2,3,4};
uninitialized_copy_n(make_move_iterator(numbers.begin()), 4, p);
my_print(p, 4);

6.1.17 引用限定符

class Hello
{
  public:
    Hello(initializer_list<int> il): numbers(il) {}
    Hello& sorted() &&
    {
      cout << "&&" << endl;
      return *this;
    }
    Hello& sorted() &
    {
      cout << "&" << endl;
      return *this;
    }
  private:
    vector<int> numbers;
};

Hello h1{1,7,2,9};
auto h2 = h1.sorted(); // 使用&
auto h3 = Hello{1,8,15,9}.sorted(); // 使用&&

6.2 友元

6.2.1 友元函数

友元函数可以访问类的私有成员和保护成员

class Hello
{
  friend void my_friend(const Hello &hello);
  private:
     int a=100;
};

void my_friend(const Hello &hello)
{
    cout<<hello.a<<end;
}

6.2.2 友元类

友元类可以访问类的私有成员和保护成员。

class Hello_Friend;
class Hello
{
    friend Hello_Friend;  
    private:
        int a=100;
};
class Hello_Friend
{
    public:
        void print_hello(const Hello &hello)
        {
            cout<<hello.a<<endl;
        }
};

6.3 成员函数

const成员函数不能修改数据成员的值。常量实例只能访问数据成员和调用const成员函数。

class Hello
{
  public:
    int a=100;
    void print_a() const
    {
        // a=1; 错误
        cout<<a<<endl;
    }
};

可变数据成员。可以被const成员函数修改

class Hello
{
  public:
    mutable int a=10;
    void set_a(int aVal) const
    {
        a = aVal;
    }
};

this指针是指向实例的指针。

class Hello
{
    public:
      Hello &run()
      {
        cout<<"run"<<endl;
        return *this;
      }
      Hello &sleep()
      {
        cout<<"sleep"<<endl;
        return *this;
      }
};

Hello hello;
hello.run().sleep();

内联成员函数

class Hello
{
    public:
        inline void print_hello();
};

重载成员函数

class Hello
{
    public:
        void print_hello();
        void print_hello(int a);
}

6.4 类的静态成员

静态数据成员除了常量外只能在类外部初始化。静态成员被所有实例共享。

class Hello
{
    public:
      static const int a = 10;
      static int b;
      static void print_hello();
};

int Hello::b=100;

静态成员可以使用作用域运算符访问,也可以被实例使用

Hello::a;
Hello::print_hello();

6.5 操作重载和类型转换

6.5.1 重载运算符

一般算术运算符使用非成员函数,一元运算符使用成员函数。因为算术运算通常会类型转换,使用非成员函数会更灵活。

class Hello
{
  friend Hello operator+(const Hello &h1, const Hello &h2);
  public:
    Hello(int numVal): num(numVal) {}
    inline int getNum() {return num;}
    Hello& operator++()
    {
      ++num;
      return *this;
    }
    Hello operator++(int)
    {
      auto tmp = *this;
      ++*this;
      return tmp;
    }
  private:
    int num = 0;
};
Hello operator+(const Hello &h1, const Hello &h2)
{
  return h1.num + h2.num; // int会使用构造函数转换
}

6.5.2 函数调用运算符

class Hello
{
  public:
    Hello& operator() ()
    {
      cout << "hello world" << endl;
      return *this;
    }
};

lambda是函数对象。函数、函数指针、lambdab表达式、bind创建的对象以及重载了函数运算符的类都是可调用对象。可调用对象可以使用function类型表示

void my_func(){}
Hello f1;
auto f2 = [&]() {};
auto f3 = my_func;
auto *f4 = my_func;
function<void()> f(f1);
f = f2;
f = f3;
f = f4;

6.5.3 类型转换运算符

通常转换bool类型。因为存在隐式的类型转换,可能会出现意想不到的结果。在构造函数使用explicit可以避免隐式转换。

class Hello
{
  public:
    int num;
    // explicit Hello(int numVal = 0): num(numVal) {}
    Hello(int numVal = 0): num(numVal) {}
    operator int () const
    {
      return num;
    }
};

Hello h1 = 9;  // 使用Hello(int) 构造函数。如果使用explicit,必须使用Hello(9)。
int b = h1 + 8; // 转换成int类型

6.6 面向对象

6.6.1 继承

类的成员分公有成员、保护成员、私有成员。公有成员可见,保护成员派生类可见,私有成员自己可见。继承分为public、protected、private继承。public继承;基类成员访问权限不变。protected继承;基类的共有公员变保存成员。private继承;基类的所有成员变私有成员。

使用final禁止继承。final类不能作为基类也不能作为派生类。

class Hello final
{};

派生类对象赋值给基类对象

class Hello
{};
class Hello2: public Hello
{};
Hello h1 = Hello2();
Hello &&h1 = Hello2();
Hello2 h2;
Hello &h1 = h2;

友元不能被继承

class Hello
{
  friend class HelloFriend;
  private:
    int num = 100;
};

class HelloFriend
{
  public:
    virtual void hello(Hello &h) const
    {
      cout << h.num << endl;
    }
};

class HelloFriend2: public HelloFriend
{
  public:
    void hello(Hello &h) const override
    {
      cout << h.num << endl; // 错误
    }
};

使用using改变个别成员的可访问性

class Hello
{
  friend class HelloFriend;
  public:
    int num = 100;
};

class Hello2: private Hello
{
  public:
    using Hello::num;
};

struct默认使用public继承;class默认使用private继承

struct Hello: Base // public继承
class Hello: Base // private继承

6.6.2 动态绑定

动态绑定的函数会在运行时才选择函数版本。通常是基类定义虚函数,子类重写虚函数,基类引用子类对象或者基类指针指向子类对象。基类的析构函数必须定义为虚函数。

class Hello
{
  public:
    virtual void hello() const;
    virtual ~Hello() = default;
};
class Hello2: public Hello
{
  public:
    void hello() const override
    {
      cout << "hello2" << endl;
    }
};
Hello2 h2;
Hello &h1 = h2;
h1.hello();
Hello *h3 = &h2;
h3->hello();

final和override

final函数不允许派生类覆盖,override会标记函数重写基类函数,编辑器会检查基类是否有对应的函数被重写,减少程序出错。

class Hello2: public Hello
{
  public:
    void hello() const override final // 派生类不能再重写
    {
      cout << "hello2" << endl;
    }
};

使用作用域运算符回避虚函数

h1.Hello::hello();

派生类的作用域嵌套在基类的作用域内。如果不是引用类型,会从声明类型作用域查找名字,如果不能找到会从基类的作用域查找,直到找到名字。找到名字后会检查类型是否匹配。如果是引用类型会判断是否是重载的函数并从对象找到重载函数。派生类的成员只要名字和基类相同,不管类型是否一样,基类的成员都会被派生类隐藏。但是重载函数必须和基类的虚函数入参一样。

class Hello
{
  public:
    int num = 100;
    virtual void hello() 
    {
      cout << "hello" << endl;
    }
};
class Hello2: public Hello
{
  public:
    double num = 3.14; // 覆盖基类的num,即使类型不一样
    void hello(int a) // 不能覆盖基类的hello
    {
      cout << a << endl;
    }
};
Hello2 h2;
Hello &h = h2;
h.hello();

6.6.3 抽象基类

含有纯虚函数的类是抽象基类。抽象基类不能被实例化,只能被派生类继承并重写纯虚函数

class Hello
{
  public:
    virtual void hello() const = 0; // 纯虚函数
    virtual ~Hello() = default;
};

7. IO库

头文件iostream主要是cin、cout、cerr、clog。

头文件fstream主要是fstream、ifstream、ofstream.

头文件sstream主要是stringstream、istringstream、ostringstream。

IO对象不能被拷贝和赋值。

7.1 文件读写

fstream操作

fstream fstrm; // 未绑定文件
fstream fstrm(s); // 绑定文件s
fstream fstrm(s, mode); // 指定mode打开文件
fstrm.open(s, mode); // 指定mode打开文件
fstrm.close(); // 关闭文件
fstrm.is_open(); // 是否打开文件

fstream的mode

in // 读模式
out // 写模式
app // 追加模式
ate // 打开文件后定位到文件末尾
trunc // 截断文件
binary // 以二进制进行IO

读写文件

ofstream out("hello.txt");
out<<"hello world"<<endl;
ifstream in("hello.txt");
string txt;
in>>txt;
cout<<txt<<endl;
out.close();
in.close();

stringstream操作

sstream strm; // 未绑定字符串
sstream strm(s); // 保存字符串的拷贝
strm.str(); // 返回strm的string拷贝
strm.str(s); // 保存字符串的拷贝

string流

string txt = "hello\nworld";
istringstream in(txt);
string tmp;
in>>tmp;
cout<<tmp<<endl;
in>>tmp;
cout<<tmp<<endl;
ostringstream out;
out<<"hello world"<<endl;
cout<<out.str();

8. 容器

8.1 容器类型

vector // 可变大小数组。支持快速随机访问,在尾部以外插入和删除元素很慢
deque // 双端队列。支持快速随机访问,在头尾位置插入和删除很快。
list // 双向链表。只支持双向顺序访问。在任何位置插入和删除很快。
forward_list // 单向链表。只支持单向顺序访问。在任何位置插入和删除很快。
array // 固定大小数组。支持快速随机访问。不能添加和删除元素。
string // 和vector类似。

8.2 容器构造函数

C c;              // 空容器
C c1(c2);         // 拷贝构造
C c1 = c2;        // 拷贝构造
C c(n);           // n个元素的容器
C c(n, val);      // n个val元素的容器
C c{a,b,c,d};     // 初始化列表
C c = {a,b,c,d};  // 初始化列表
C c(b,e)          // b和e迭代器范围内的元素进行初始化

8.3 容器操作

c1.swap(c2);            // 容器交换元素。不影响之前的引用和迭代器
swap(c1, c2);

c.size();               // 容器大小
c.empty();              // 容器是否为空
c.push_back(val);       // 在尾部添加元素
c.push_front(val);      // 在头部添加元素
c.pop_back();           // 删除尾部元素
c.pop_front();          // 删除头部元素
c.clear();              // 删除所有元素
c.erase(iter);          // 删除iter迭代器之前的元素
c.erase(b,e);           // 删除b和e迭代器范围内的元素
c.begin();              // 返回头部迭代器
begin(c);            
c.end();                // 返回尾部迭代器
end(c);
c.insert(iter,val);     // 在iter迭代器的位置插入元素,返回插入位置的迭代器。
c.insert(iter,n,val);   // 在iter迭代器的位置插入n个val元素,返回插入位置的迭代器。
c.insert(iter,b,e);     // 在iter迭代器的位置插入b和e迭代器之前的元素,返回插入位置的迭代器
c.insert(iter,il);      // 在iter迭代器的位置插入初始化列表的元素。返回插入位置的迭代器。
c.emplace(iter,args);   // 在iter迭代器的位置使用args参数列表来构造元素并插入。返回插入位置的迭代器。
c.emplace_front(args);  // 在头部使用args参数列表来构造元素并插入。
c.emplace_back(args);   // 在尾部使用args参数列表来构造元素并插入。
c.at(n)                 // 返回n位置元素的引用
c[n]                    // 返回n位置元素的引用
c.back()                // 返回尾部元素的引用
c.front()               // 返回头部元素的引用 

注意:对容器进行插入删除,可能会造成之前的迭代器失效。所以要使用返回的迭代器来更新操作前的迭代器。

vector<int> v1(10);
auto iter = begin(v1) + 2;
iter = v1.insert(iter, 100);
cout<<v1[2]<<endl;

8.4 容器适配器

容器适配器有栈适配器和队列适配器

stack<int> a;
stack<int, vector<int>>a(v1);
queue<int> b;
queue<int, deque<int>>b(d1);

9. 泛型算法

在头文件numeric和algorithm中。

9.1 容器求和

accumulate(v1.cbegin(), v1.cend(), 0);

9.2 容器元素比较

equal(v1.cbegin(), v1.cend(), v2.cbegin());

9.3 填充容器

fill(v1.begin(), v1.end(), 9);
fill_n(v1.begin(), 3, 8);

9.4 拷贝容器

int a[] = {1,2,4,5,6,7};
int b[sizeof(a) / sizeof(*a)];
copy(begin(a), end(a), b);

9.5 替换元素

replace(v1.begin(), v1.end(), 6, 99);
// v1元素不变,vec拷贝v1元素且替换6为99
replace_copy(v1.cbegin(), v1.cend(), back_inserter(vec), 6, 99);

9.6 排序元素

sort(strs.begin(), strs.end());
sort(strs.begin(), strs.end(), [](const string &v1, const string &v2) -> bool {
  return v1.size() < v2.size();
});

9.7 去重元素

auto iter = unique(strs.begin(), strs.end());
strs.erase(iter, strs.end());

9.8 lambda表达式

[capture list](parameter list) -> return type { function body }

int sz = 2;
auto func = [sz](const string &str)
{
  return str.size() > sz;
};
cout << func("199") << endl;

lambda捕获

值捕获:[v]

引用捕获:[&v]

隐式捕获

[=]函数体捕获的变量为值捕获

[&]函数体捕获的变量为引用捕获

[=,&v1]v1为引用捕获,其他为值捕获

[&,v1]v1为值捕获,其他为引用捕获

9.9 查询元素

auto iter = find(strs.cbegin(), strs.cend(), "aj");
int sz = 2;
auto iter = find_if(strs.begin(), strs.end(), [sz](const string &value) {
  return value.size() > sz;
});

9.10 遍历元素

for_each(strs.cbegin(), strs.cend(), [](const string &value) {
  cout << value << " ";
});

9.11 bind函数

bind函数用于适配函数,使用占位符和默认参数来生成一个函数, 在头文件functional。

bool check(const string &str, string::size_type sz)
{
  return str.size() < sz;
}

auto check2 = bind(check, std::placeholders::_1, 5);
cout << check2("193") << endl;

其中_1为返回的可调用函数的入参位置。

10. 关联容器

map关联数组,保存关键字-值对
set关键字即值,只保存关键字的容器
multimap关键字可重复出现的map
unordered_map用哈希函数组织的map
unordered_set用哈希函数组织的set
unordered_multimap哈希组织的map,关键字可以重复出现
unordered_multiset哈希组织的set,关键字可以重复出现

它们在map、set、unordered_map、unordered_set头文件。

map的元素是一个pair的键值对对象。first成员为键,second成员为值。

10.1 遍历容器

map<string, string> authors = {{"xue", "19"}, {"op", "99"}};
for (auto &&pair : authors)
{
  cout << pair.first << " " << pair.second << endl;
}

10.2 添加元素

auto ret = authors.insert({"mk", "199"});
if (ret.second)
{
  cout << ret.first->first << " " << ret.first->second << endl;
}
authors.emplace("mk", string(10, 'c'));
authors.insert(b, e); // 添加b和e迭代器的范围的pair
authors.insert(p, pair) // 在迭代器p位置添加pair
authors.insert({{"mk", "111"}, {"lo", "444"}}) // 添加初始化列表

插入的结果是一个pair对象,frist为插入元素的迭代器,second为插入是否成功。

10.3 删除元素

authors.erase("mk"); // 指定关键字
authors.erase(iter); // 指定迭代器
authors.erase(b, e); // 指定迭代器b和e的范围

10.4 下标访问

下标访问的元素不存在会插入新元素。at访问会抛出异常。

map<string, int> mymap;
mymap["hello"] = 10; // 插入新元素
mymap.at("world") = 19; // 抛出异常

10.5 查询元素

使用find或者count来查询元素是否存在。

auto iter = mymap.find("hello");
if (iter != mymap.end())
{
  cout << iter->second << endl;
}
auto count = mymap.count("hello2");

multimap和multiset允许重复关键字,需要同时使用count和find来遍历元素

multimap<string, int> my_multi_map = {{"hello", 1}, {"hello", 2}, {"hello2", 3}};
auto iter = my_multi_map.find("hello");
auto count = my_multi_map.count("hello");
while (count)
{
  cout << iter->second << endl;
  ++iter;
  --count;
}

使用lower_bound和upper_bound来获取搜索的迭代器范围

multimap<string, int> my_multi_map = {{"hello", 1}, {"hello", 2}, {"hello2", 3}};
for (auto begin = my_multi_map.lower_bound("hello"), end = my_multi_map.upper_bound("hello"); begin != end; begin++)
{
  cout << begin->second << endl;
}

使用equal_range获取搜索的范围

for (auto pos = my_multi_map.equal_range("hello"); pos.first != pos.second; pos.first++)
{
  cout << pos.first->second << endl;
}

10.6 无序容器

无序容器使用hash函数组织元素。无序容器的关键字类型需要实现==运算符和hasher函数。可以使用标准库的hash模板来生成hash值。

auto hashCode = hash<string>()("123");

11. 动态指针

为了方便使用动态内存,标准库提供了两种智能指针来管理动态对象。shared_ptr允许多个指针指向同一个对象;unique_ptr独占指向的对象。weak_ptr是弱引用,用来指向shared_ptr管理的对象。它们定义在memory头文件。

11.1 shared_ptr

shared_ptr采用引用计数的方式管理动态内存。当存在n个shared_ptr指向同个对象,则对象的引用计数为n,shared_ptr实例销毁时,引用计数减1,所以实例销毁,引用计数为0;释放对象内存。shared_ptr是一个对象,在析构函数管理引用计数。

shared_ptr<int> sp; // 空智能指针,可以指向类型为T的对象。
unique_ptr<int> up;
p // 可以用来条件判断。
p.get() // 获取管理的指针
swap(p, q) // 交换智能指针
p.swap(q)

11.2 初始化shared_ptr

shared_ptr<int> sp = make_shared<int>(29);
shared_ptr<int> sp(new int(2024));
shared_ptr<T> sp(p, d); // pT类型指针,d为回收T类型指针的函数。
sp.reset() // sp指向空,之前指向的对象的引用计数减1.
sp.reset(q) // 指向q的对象,q对象引用计数加1,之前指向的对象引用计数减1.
sp.reset(q, d) // 提供回收q类型指针的函数。默认是使用delete操作符。

不要混用普通指针和智能指针。

1. delete普通指针后,使用智能指针出错。

2. 智能指针销毁后,自动delete普通指针。使用普通指针出错。

不要用get初始化另一个智能指针。

1. 智能指针销毁后,自动delete指针,使用另一个智能指针出错。

11.3 unique_ptr

unique_ptr不能被多个实例引用。

unique_ptr<int> up; // 指向空的unique_ptr
unique_ptr<int> up(new int(100));
unique_ptr<T, D> up(p, d); // 指向空。 d为类型D的对象,用于删除T类型
up.release(); // 返回指向的指针并指向空。
up.reset(q); // 指向q指针。

11.4 weak_ptr

week_ptr用来解决循环引用的问题,weak_ptr指向的shared_ptr指针,引用计数不会增加。

weak_ptr<int> wp = sp;
weak_ptr<int> wp(sp);
wp.reset(); // 指向空
wp.lock(); // 指向sp的引用计数为0则返回空的shared_prt,否则返回指向的shared_ptr

11.5 动态数组

int *p = new int[10]; // 10个未初始化的int
int *p = new int[10](); // 10个初始化为0的int
int *p = new int[10]{1,2,3,4} // 初始化为1,2,3,4,后面全为0
delete [] p;
p = nullptr;

shared_ptr需要提供delete函数来释放动态数组,unique_ptr不需要提供。

shared_ptr<int>sp2(new int[10](), [](int *p) {delete [] p;});
unique_ptr<int[]>up2(new int[10]());

11.6 allocator

allocator将分配内存和初始化分离开来

allocator<string> a;      // 定义一个string的allocator对象
auto q = a.allocate(10);  // 分配10个字符串
a.construct(q, 10, '1'); // 构造第一个字符串
a.destroy(q); // 执行q的析构函数
a.deallocate(q, 10); // 释放之前分配的10个字符串内存

11.7 allocator算法

allocator<int> my_alloc;
vector<int> numbers{1,2,3,4,5};
auto p1 = my_alloc.allocate(5);
uninitialized_copy(numbers.cbegin(), numbers.cend(), p1);
my_print(p1, 5); // 1,2,3,4,5
uninitialized_fill(p1, p1 + 5, 10);
my_print(p1, 5); // 10,10,10,10,10
uninitialized_copy_n(numbers.cbegin(), 5, p1);
my_print(p1, 5); // 1,2,3,4,5
uninitialized_fill_n(p1, 5, 10);
my_print(p1, 5); // 10,10,10,10,10

12. 模板与泛型编程

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第 1 章:C++ 基础知识 此模块将向您介绍 C++,包括其历史、设计理念以及几个最重要的功能。此模块简要概述几个 C++ 功能,包括 C++ 程序的一般形式、一些基本控制语句和运算符。它不会介绍太多细节,而会重点介绍对所有 C++ 程序都通用的一般概念。 第 2 章:数据类型和运算符简介 编程语言的核心在于其数据类型和运算符。不出您所料,C++ 支持大量数据类型和运算符,使其适合的编程范围非常广泛。此模块对 C++ 基本数据类型及其最常用运算符进行探讨。我们还将进一步了解变量,并研究表达式。 第 3 章:程序控制语句 此模块讨论用于控制程序执行流的语句。有三种类别的程序控制语句:选择语句,包括 if 和 switch 语句;迭代语句,包括 for、while 和 do-while 循环;以及跳转语句,包括 break、continue、return 和 goto 语句。 第 4 章:数组、字符串和指针 此模块讨论数组、字符串和指针。数组是变量的集合,这些变量具有相同的类型,由一个公用名引用。数组为创建相关变量的列表提供了一种便利方法。C++ 语言不定义内置字符串数据类型。相反,字符串作为字符数组实现。指针是包含内存地址的对象。通常,指针用于访问另一个对象的值。 第 5 章:函数简介 此模块开始深度探讨函数。函数是 C++ 的构建基块,深入理解函数是成为成功 C++ 编程人员的基础。下面,您将了解如何创建函数。您还将了解传递参数、返回值、局部变量和全局变量、函数原型和递归。 第 6 章:进一步了解函数 此模块继续探讨函数。它讨论了 C++ 的三个最重要的函数相关主题:引用、函数重载和默认参数。 第 7 章:更多数据类型和运算符 此模块返回到数据类型和运算符的主题。除了您到目前为止已在使用的数据类型,C++ 还支持其他几种数据类型。其中一些数据类型由已知类型加上修饰符组成。其他数据类型包括 enumeration 和 typedef。C++ 还提供多个附加运算符,极大地扩展了 C++ 可以应用到的编程任务范围。 第 8 章:类和对象 类是 C++ 的基本封装单位。类用于创建对象。若要编写面向对象的程序,需要使用类。类和对象对于 C++ 非常重要,因此本书其余内容大部分都或多或少与它们相关。 第 9 章:进一步了解类 此模块继续探讨模块 8 中谈到的类。它涉及很多与类相关的主题,包括重载构造函数、传递对象到函数以及返回对象。它还介绍一种特殊类型的构造函数(称为复制构造函数),这种函数在需要对象副本时使用。接下来介绍友元函数,然后是结构和联合,以及 this 关键字。此模块最后介绍运算符重载,这是 C++ 中最吸引人的功能之一。 第 10 章:继承、虚函数和多态性 此模块讨论 C++ 中与面向对象编程直接相关的三个功能:继承、虚函数和多态性。继承是允许一个类继承另一个类特性的功能。虚函数是在继承的基础上构建的。虚函数支持多态性(面向对象编程的“一个接口,多种方法”原理)。 第 11 章:C++ I/O 系统 C++ I/O 系统非常大,无法在此讨论每个类、函数或功能,不过此模块将介绍最重要和最常用的部分。具体而言,它说明如何输入或输出所设计类的对象。它还介绍如何设置输出格式以及如何使用 I/O 操纵器。此模块最后讨论文件 I/O。 第 12 章:异常、模板和其他高级主题 最后一个模块将介绍几个重要的、高级 C++ 主题,包括异常处理、模板、动态分配和命名空间。另外还介绍运行时类型 ID 和转换运算符。完成此模块后,您将掌握这种语言的核心元素,能够开始编写实际程序。 掌握检查的答案 附录 A:预处理器 预处理器是编译器的一部分,在将源代码实际转换为对象代码之前,预处理器对程序执行各种文本操作。可以为预处理器提供文本操作命令。这些命令称为预处理器指令,它们实际上不是 C++ 的组成部分,但扩展了 C++ 编程环境的范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值