Java程序员的C++回归路(二)

接前: 之前记录的笔记,终于想起来上传完整。

第7章: 类

定义抽象数据类型

任何对成员对象的访问都可以解释为使用this来访问,即this->member。

=default :默认构造函数。如果定义在类内部,则默认是内联的。

struct和class

使用struct和class定义类,唯一的区别在于默认的访问范围,struct是public而class是private

友元函数

类的特性

定义类型成员

在类中使用格式:

public:
    typedef std::string::size_type pos;

来定义类型成员

类内部定义的函数默认是inline的。

inline标识可以在函数声明和定义的地方使用,但是一般在函数定义的时候使用

const成员函数的返回值必须是const引用

const函数可以重载:

Screen a();
const Screen a() const;

隐式转换

以Sales_data为例,如果定义了构造函数Sales_data(const string&),那么在使用其成员函数combine(sales_data)时,可以使用combine(string&)来做到隐式转换。

可以使用explict关键字来避免隐式转换。

第8章 IO

打开文件

ifstream:读

ofstream:写

fstream:以读/写的方式打开文件

如果调用open()失败,则failbit将被设置。

fstream对象销毁时,close方法将自动被调用(在循环中,会自动关闭)

文件模式

Mode Meaning
out Open for output
in Open for input
app Seek to the end for every write
trunc truncate the file
binary Do IO operations in binary mode

默认的,使用out模式的文件将被删除即使没有指定trunc模式。为了避免文件内容被删除,要么指定app模式追加到文件中,要么指定in模式使得文件同时可以使用in和out模式。

字符串流

strm.str() 返回strm保存的字符串拷贝

strm.str(s) 将s保存到strm中,返回void

ostringstream

第九章 容器

容器访问

forward_list不支持back访问

pop()函数返回void。

容器size管理

c.shrink_to_fit()
c.capacity()
c.reserve(n)

q.emplace(args):通过使用args构造的对象,插入到容器中,或者放到优先级容器的对应位置中。

第10章 算法

大部分的算法实现定义在algorithm中,有些一般化的数学算法定义在numeric头文件中。

lambda表达式

lambda表达式的格式为:

[capture list] (parammeter list) -> return type{function boy}

[]中是使用的local variable,capturelist中仅仅包含定义在函数体中非静态的变量,cout等变量不用声明在capture list中。

foreach算法

bind的使用

auto g = bind(f, a, b, _2, c, _1)

其中,bind定义在functional头文件中,调用g()将调用f()。

调用g(X, Y)将调用f(a, b, Y, c, X)。

bind可以将一个函数封装为另一个callable对象,提供给调用。

绑定引用

使用ref()和cref()返回应用和静态引用。(定义在functional头文件中)

for_each(words.begin(), words.end(), bind(print, ref(os), _1, ''))

第12章 动态内存

使用new和delete来动态分配和释放内存。

智能指针

new

Delete

Delete不是由new创建的对象,或者多次删除同一个对象的行为未定义。

管理动态内存容易出现的问题:

1.忘记delete内存。

2.使用一个已经被delete的对象。

3.Delete一个对象多次。

在使用delete后一般使用p = nullptr来设置指针的值。

不能将普通指针直接转换为智能指针,而是智能通过智能指针的构造函数来实现。

如果在new 和delete之间抛出了未捕获的异常,那么将出现内存泄漏。

可以使用智能指针解决这个问题,例如shared_ptr p(&c, end),即传入一个函数,用于free内存。

unique_ptr

使用new来初始化unique_ptr。

使用release和reset来传递ownership。reset()重置,release使得当前指针为空。

release()返回的是普通指针。

weak_ptr

weak_ptr不控制对象的声明周期,而是指向由shared_ptr管理的对象。

要获得对象,可以使用wptr.lock返回shared_ptr指针

第13章 copy control

拷贝构造

拷贝初始化时,需要拷贝构造函数或move constuctor。

拷贝初始化发生在:

  1. 使用=
  2. 给非引用的参数传值
  3. 函数返回非引用结果
  4. 括号初始化数组或者aggregate 类

Aggregate class:所有成员变量都是public,未定义构造函数,可以使用{}来初始化。

编译器在编译可能会跳过拷贝构造而是直接使用参数构造对象。

拷贝赋值

赋值操作通常给左操作符返回引用。

析构函数

析构函数在一个类的对象被销毁的时候调用。

在一个函数执行完成后,函数体中构造的对象将一一被销毁(对象、智能指针所指向的对象),对象的析构将调用成员的析构函数。当指针或引用指向作用域之外的对象时,不执行析构函数。

对拷贝的控制

拷贝构造、拷贝赋值、析构函数、move构造、move赋值

一般来说,当定义了其中一个(如析构函数)时,也需要定义其他的函数

如果仅仅定义了一个,那么在执行拷贝后,多个对象的指针指向的是同一块内存,在delete时将出现错误。

在定义的拷贝构造函数中,需要申请内存,并且复制传入参数的值。这样在copy constructor执行完成后,参数将被析构,旧的内存将被释放。

一般来说,定义了拷贝构造函数,也需要定义拷贝赋值,反之亦然。

否则对象的行为将不一致。

使用=default生成默认的函数,当在头文件中使用=时,函数为内联函数。

禁用拷贝构造和拷贝赋值

使用=delete。delete必须使用在函数的声明中。

如果一个对象的成员不能默认的构造、拷贝、赋值和析构,那么这个对象对应的函数也将是delete的。

在新标准之前,可以将这些函数定义为private,这样也可以禁用对应的函数。

Swap

与拷贝函数之类的不同,swap函数不是必须的。但是实现自己的swap函数是重要的优化手段,特别是在申请资源的时候。

std::move

许多内部函数中,定义了move constructor。在使用string的move constructor时,实际上拷贝的是指向string的指针,源string是valid状态。

调用move不会拷贝由string管理的内存,而是把ownership由string交给elem。

对象移动

右值引用

int i = 42;
int &r = i; // ok: r refers to i
int &&rr = i; // error: cannot bind an rvalue reference to an
lvalue
const int &r3 = i * 42; // ok: we can bind a reference to const to an
rvalue
int &&rr2 = i * 42; // ok: bind rr2 to the result of the multiplication

右值引用绑定右值。

如果一个类中有可用的拷贝构造而没有move constructor,对象将会使用拷贝来代替move construction。

使用std::move调用move constructer,将rvalue赋值给左值调用move assignment

Because a moved-from object has indeterminate state, calling std::move on

an object is a dangerous operation. When we call move, we must be

absolutely certain that there can be no other users of the moved-from object.

Judiciously used inside class code, move can offer significant performance

benefits. Casually used in ordinary user code (as opposed to class

implementation code), moving an object is more likely to lead to mysterious

and hard-to-find bugs than to any improvement in the performance of the

application.

定义了函数的两个版本的情况下,根据rvalue或lvalue决定调用。

第14章 运算符重载

基本概念

可以重载的运算符

运算符重载的选择:

1.= [] ()->必须定义为member

2.复合赋值运算符应该作为member,比如+=

3.修改对象的状态的运算符定义为member

4.对称的操作符,比如数学、bit操作符通常应该定义为nonmember函数。

例如:

string s= "test";
string u = "hi" + s;

在这里,“hi”是const char *,没有重载+运算符,但是这里等于operator+("hi", s)的调用,只要至少一个运算符是类类型,那么两个操作符都可以隐式转换为string。

IO运算符重载不能作为成员函数。

数学运算符重载

一般的,由于左操作数和右操作数都允许转换,所以不定义为成员函数。另一方面,一般不修改参数,参数一般定义为const。

定义数学操作符时,一般通过复合运算符实现。

赋值运算符

下标运算符

定义下标运算时,一般需要定义两个版本,一个是const版本一个是非const版本

自增/减运算符

一般定义为成员函数

一般定义前置和后置两个版本

前置版本:

StringBlobPtr& operator++(); // 一般返回引用,且没有参数
StringBlosPtr& operator++(int); // 返回值,且参数为int,不使用int参数,所以一般不设置名称

成员访问操作符

函数调用操作符

使用函数调用操作符可以让类本身变为一个可以调用的对象

lambda表达式实际上就是函数对象

第15章 面向对象编程

struct默认是public继承,class默认是privat继承。

当派生类有函数名与基类相同时,基类的函数将被隐藏,使用d.base::func()的方式进行调用。

构造函数和拷贝控制

虚析构函数

Warning:如果delete的指针本身的析构函数不是virtual,行为未定义。

派生类在执行构造函数时,先执行父类的构造函数(无论父类是否提供默认构造函数,只要能访问或不是deleted),派生类在执行析构函数时,销毁自己的member,再一级级销毁父类的member。

如果定义了copy constructor,那么将不再默认生成move constructor。

如果定义了虚析构函数,那么将禁用自动生成的move函数。

如果需要定义,则需要显示定义。定义了move构造函数的同时,需要构造对应的copy版本。

class Quote {
  public:
  Quote() = default; // memberwise default initialize
  Quote(const Quote&) = default; // memberwise copy
  Quote(Quote&&) = default; // memberwise copy
  Quote& operator=(const Quote&) = default; // copy assign
  Quote& operator=(Quote&&) = default; // move assign
  virtual ~Quote() = default;
  // other members as before
};

构造函数需要初始化父类和自身的成员,但是析构函数仅仅需要释放自己申请的资源,父类的成员将被隐式销毁

派生类实现拷贝、Move Constructor

class Base { /* ... */ } ;
class D: public Base {
  public:
  // by default, the base class default constructor initializes the base part of an object
  // to use the copy or move constructor, we must explicitly call that
  // constructor in the constructor initializer list
  D(const D& d): Base(d) // copy the base members
  /* initializers for members of D */ { /* ... */ }
  D(D&& d): Base(std::move(d)) // move the base members
  /* initializers for members of D */ { /* ... */ }
};

By default, the base-class default constructor initializes the base-class part of

a derived object. If we want copy (or move) the base-class part, we must

explicitly use the copy (or move) constructor for the base class in the

derived’s constructor initializer list.

在容器中使用指针或动态指针,用以调用保存派生类的对象。

第16章 模板和泛型编程

函数模板

/*
 * 模板函数
 */
template<typename T>  // T是模板参数列表
int compare(const T &t1, const T &t2){
    if (t1 < t2) return -1;
    else if(t1 > t2) return 1;
    else return 0;
}

编译器通过类型推断来初始化泛型模板,创建一个模板的实例。

参数列表是常引用。

参数列表中可以使用typename和class,这两者有着相同的含义。但是类型一定要使用typename或class进行修饰。

使用无类型的模板,参数必须是常量表达式:

template <unsigned M, unsigned N>  // 在这里使用传入char[]的长度替换M/N,注意长度是n + 1,由于null terminator
int compare(const char (&p1)[M], const char (&p2)[N]){
    return strcmp(p1, p2);
};

Inline

template<typename T> inline T min(const T&)

当遇到template的定义时,编译器不生成代码。仅仅当初始化特定的实例时生成代码。

模板函数的定义通常放在头文件中,因为需要生成代码。

模板编译初始化的过程:

  1. 编译模板
  2. 检查使用模板时的错误
  3. 初始化

类模板

TypeName的使用:

typename关键字告诉编译器将一个特殊的名字结识为一个类型,在下列情况下需要对name使用typename关键字:

  1. 一个唯一的name,嵌套在另一个类型中
  2. 依赖于一个模板参数。

例如:

template <typename T>
struct C{
  typedef T value_type;
}
typedef typename C::value_type other_name

当使用类型作为返回值时,使用typename C::value_type声明返回值。

编译器对每一个泛型类生成不同的class。

在源文件中定义成员函数时,格式为:

template <typename T>
ret-type Blob<T>::member-name(parm-list)

成员函数仅仅当被使用的时候才被初始化。

在类中的代码,可以使用类名直接使用,而不需要ClassName

在模板类中,可以指定friend的范围是所有的实例还是某个实例:

// forward declaration necessary to befriend a specific instantiation of a template
template <typename T> class Pal;
class C { // C is an ordinary, nontemplate class
friend class Pal<C>; // Pal instantiated with class C is a friend to
C
// all instances of Pal2 are friends to C;
// no forward declaration required when we befriend all instantiations
template <typename T> friend class Pal2;
};
template <typename T> class C2 { // C2 is itself a class template
// each instantiation of C2 has the same instance of Pal as a friend
friend class Pal<T>; // a template declaration for Pal must be in
scope
// all instances of Pal2 are friends of each instance of C2, prior declaration needed
template <typename X> friend class Pal2;
// Pal3 is a nontemplate class that is a friend of every instance of C2
friend class Pal3; // prior declaration for Pal3 not needed
};

为类模板使用别名:

template<typename T> using twin = pair<T, T>;
twin<string> authors;

可以指定部分部分为参数:

template <typename T> using partNo = pair<T, unsigned>;
partNo<string> books; // books is a pair<string, unsigned>
partNo<Vehicle> cars; // cars is a pair<Vehicle, unsigned>
partNo<Student> kids; // kids is a pair<Student, unsigned>

模板默认参数

// compare has a default template argument, less<T>
// and a default function argument, F()
template <typename T, typename F = less<T>>  // less是algorithm库中的函数类
int compare(const T &v1, const T &v2, F f = F())
{
if (f(v1, v2)) return -1;
if (f(v2, v1)) return 1;
return 0;
}

模板与重载

重载时的规则:

When there are several overloaded templates that provide an equally good

match for a call, the most specialized version is preferred.

When a nontemplate function provides an equally good match for a call as a

function template, the nontemplate version is preferred.

第18章 大型程序

异常处理

如果基类声明了不抛出异常(noexcept),那么派生类的声明必须和基类一致。

The only operations that the exception types define are the copy constructor,

copy-assignment operator, a virtual destructor, and a virtual member named what.

The what function returns a const char* that points to a null-terminated character

array, and is guaranteed not to throw any exceptions.

自定义异常:

class out_of_stock: public std::runtime_error {

public:

explicit out_of_stock(const std::string &s):

std::runtime_error(s) { }

};

class isbn_mismatch: public std::logic_error {

public:

explicit isbn_mismatch(const std::string &s):

std::logic_error(s) { }

isbn_mismatch(const std::string &s,

const std::string &lhs, const std::string &rhs):

std::logic_error(s), left(lhs), right(rhs) { }

const std::string left, right;

};

使用自定义异常的方式和使用标准库异常类似。

命名空间

命名空间必须是唯一的,可以定义在全局也可以定义在其他的命名空间内。

命名空间的}后不加;

多重继承和虚继承

猜你喜欢

转载自www.cnblogs.com/kode/p/9320906.html