避免重复引用库

func.h文件这么写

#ifndef FUNC_H__
#define FUNC_H__
//正文
#endif

const关键字:

const variables

告诉编译器这个变量只读,必须要声明和初始化同时进行:

const int N{100005};
const int N = 100005;

除非用extern关键字,涉及到一些编译的东西,暂时不用,不管。

函数值传参时用const:

void f(const int x) {
}

告诉编译器这个x在f里不能被改。

函数引用|地址传参时用const:

表示地址上的值不能被改。

const放不同的位置还有不同效果

注意const int x和int const x的效果是完全一样的(const:常量)const int p 和int const p是不一样的,前者p不可变,p可变;后者p不可变,p可变。
总之,在const之后的东西(*p或者p)是常量,不可变

成员变量+const

和外面的一个意思,只读。

但因为某些原因,不能直接拿来开数组,加static就行。

可以有限制地先声明再定义(不用加extern),限制即初始化时只能在初始化列表里,而不能在构造函数体时。

比如这么写,在构造函数开始前的初始化列表写:

class fred 
{ 
    const int size;
public:
    fred();
};
fred::fred() : size{100}
{
  // constructor body
}

暂不理解这么写有毛用。

成员函数后+const

即告诉编译器这个函数是个对该类下变量只读的函数,除非定义成员变量前+mutable。

因此const成员函数不能call 非const成员函数。

class Test {

int func() const;

};

int Test :: func() const {
    return 0;
}

as_const 和 const_cast

暂不理解。

fstream基础操作


简单来说fstream就是stream读入输出流的文件版本(平时用的cin本身是个ifstream,cout是个ofstream),关系就像scanf和fscanf那样,不过stream之流是C++的特性。

fstream提供了三个类:

  1. ifstream
  2. ofstream
  3. fstream

可以定义以下类型:

  • ios::in 为输入(读)而打开文件;
  • ios::out 为输出(写)而打开文件;
  • ios::ate 初始位置:文件尾;
  • ios::app 所有输出附加在文件末尾;
  • ios::trunc 如果文件已存在则先删除该文件;
  • ios::binary 二进制方式;
  • ios::nocreate:不建立文件,所以文件不存在时打开失败;
  • ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败;

定义这些类型方法如下(在open时定义):

//1
ofstream file;
file.open ("example.bin", ios::out | ios::app | ios::binary);
//2
ofstream file ("example.bin", ios::out | ios::app | ios::binary);

ofstream, ifstream 和 fstream可以理解为默认带了不同的类型。

  1. ofstream 默认方式 ios::out | ios::trunc
  2. ifstream 默认方式 ios::in
  3. fstream 默认方式 ios::in | ios::out

当然只要你定义了类型,这些默认的就会被覆盖。

这三个类有下面这些基础成员函数调用:

bool is_open(); //返回值表示文件是否被顺利打开
bool bad(); //读写过程中是否出错(操作对象没有打开,写入的设备没有空间)
bool fail(); //读写过程中是否出错(操作对象没有打开,写入的设备没有空间,格式错误--比如读入类型不匹配)
bool eof(); //读文件到达文件末尾,返回true
bool good(); //以上任何一个返回true,这个就返回false
void close (); //关闭文件,但不加这个destrutor也会帮你自动释放。

读入输出方法同cin,cout,把cin,cout换成对应的名称即可。

缺省参数(Default arguments):

void Simulation(int x, int y = 0, int z = 0);

缺省的只能是末尾的连续一段变量(不然会二义),调用时可以省略末尾的连续一段变量,自动用缺省值代替。

namespace

namespace不要在头文件里打开

static关键字

static class members

OOP课程笔记-1-基本语法_构造函数

static member variables:

  1. 用于数组长度命名(代替enum)
  2. 用于类的对象计数

注意(用于类的对象计数):
类里所有的变量都是先声明,在创造对象是才会被定义。

所以类里的静态成员变量必需在类外定义:

int X::x=0;

static member function:

在类里,带有static关键字的函数只能访问也含有static的变量和函数。

reference:

即小学就学过的 & 的符号。

void swap(int &x, int &y) {
  ...
}

它的作用就相当于别名,是为了替代麻烦的指针操作而存在的。

&x的大小就是指针的大小。

当然也可以直接定义这样的变量并初始化。
注意必须定义的同时初始化,因为空的reference不行(空指针可以):

int x;
int &a{x};

下面这么写就不行了:

int x;
int &a;
a = x;

也可以给函数个引用:

string& larger(string &a, string &b) {
	return a > b ? a : b;
}

int main() {
	string a{"123"};
	string b{"321"};
	larger(a, b) = "567";
	cout << a << endl;
	cout << b << endl;
}

reference原则:

永远不要引用传给一个将来会被删除的变量(栈变量)。

用&加速:

我们知道函数传参时,如果参数是个很大的类,这个会被复制一遍,浪费时间,所以如果没有什么对类的修改,可以加&。

以防万一,可以const & 一起用。

用auto &遍历数组并修改:

for (auto &t : temperatures) {
  t = 0;
}

Copy constructor

即在复制或者传参时的一个构造函数。

格式:

X(const X&boj){...}

如果没有写,编译器会帮忙合成一个复制构造函数,这个复制构造函数原理是位复制(即全部内容照搬)。

当有指针时,在delete时可能会重复,从而暴毙。

设计原则上需避免copy constructor。

可以一个空的在private里或者用delete关键字:

A(const A& a) = delete;

lvalue, rvalue, move constructor

lvalue:

能放等号左边的东西。

rvalue:

能放等号右边的东西
比如说一个函数:

inf f(){...}

就只是右值不是左值。

这个值可能在用一次后就被销毁了。

const and non-const reference

non-const reference 只接受左值。
const reference 接受左值和右值。

比如:

int &r = f();

是不行的

const int &r = f();

是可以的。

rvalue reference

关键字 &&
其只接受将要销毁的有值。

比如:

int lval = 2;             // lval is an lvalue 
int f() {...}             //f() is rvalue
int&& ref_rval = 5;       // OK: binds to an rvalue 5
int&& ref_rval2 = lval;   // Error: rvalue ref cannot bind to lvalue
int&& ref_rval3 = lval+5; // OK: binds to an rvalue  
int&& ref_rval4 = f();    // OK: binds to an rvalue

move constructor

为了提高效率,如果在x=y这样一个操作后y的值没有用了

可以用x=std::move(y)来完成,此时y成为一个rvalue reference。

当然,当一个函数返回值就是该类时,自动是rvalue reference。

针对这样右值引用的复制构造函数是不一样的,我们称为move constructor。

语法:

X(X && obj){
}

有指针时,可以把 && obj 里的指针复制后再设为nullptr。

构造函数或者析构函数被定义时,编译器就不会合成移动构造函数。

protected 对象:

private < protected < public

protected 对象可以给派生类使用,但不能在外面使用。

namehiding

在派生类中,如果出现了f这个函数,那么基类中所有叫f的函数都会被hide起来。

如果想在外面用基类中的叫f的函数,可以在加using BaseClass :: f。

这样会expose BaseClass 中所有叫f的函数。

如果只想expose一个,可以用forwarding functions。

即在派生类手动写一个相同的函数,返回基类中函数的值。

也可以把不想用重新声明为private。

比如:

class A {
public:
	void f(int x) {
		printf("int int\n");
	};
	void f(string x) {
		printf("string\n");
	}
};

class B : public A {
public:
	using A :: f;
	void f() {
		printf("Null\n");
	}
private:
	void f(int x);
};

class C : public A {
public:
	void f() {
		printf("Null\n");
	}
	void f(string x) {
		return A :: f(x);
	}
};

enum

格式为:

enum type {
  a, b, c=100
}

其中a,b,c的值默认是0,1,2,...

可以自己赋值,值必须是整数。

然后每个元素就与对应的值建立了联系,b 等价于 (type) 1

他们本身也对应一个整数,可以拿来用。

比如

int x = c;
if(x == c) {

}
int arr[c];

但不能被一个整数直接赋值。

type并不完全像class或者struct中的类名,他可以是一个枚举变量的代指,语法为:

void f(type) {}
f(a);

friend:

友元函数:

一个类的私有成员在类外是无法被访问的。

但是有些类外函数频繁需要访问类的成员,这时可以用友元函数。

友元函数不是成员函数,而是全局函数。

关于一个类的友元函数需要在类里声明,前面加上friend。

定义可以在里或者外(在外可以不用加friend),该函数可以访问private成员,但是由于没有this指针,所以需要在参数里传对象的引用来访问。

class ClassA {
private:
	int x;
public:
	friend void Add(ClassA &a, int y);
};

void Add(ClassA &a, int y) {
	a.x += y;
}

类做友元:

class ClassB {
	friend ClassA;
};

ClassB中可以访问ClassA的对象的private成员。

继承中的屁事:

继承中的构造函数:

假设一个派生类D继承了多个基类A,B,C,

class D : public A, public B, public C {

};

那么在生成D的对象时,

那么会先调用A,B,C的构造函数。

D的构造函数就是为A,B,C的构造函数传递参数的通道,必须在D的构造函数的初始化列表上来构造A,B,C,如果没写,就是用默认的没参数的那个。

注意,构造函数调用的顺序就是(A,B,C),即继承时的顺序,和初始化列表中的无关。

A,B,C也是派生类的话,递归考虑即可。

继承中的析构函数:

调用顺序与构造函数相反。

先调用派生类的,再调用派生类里内嵌对象的,最后基类。

继承中的拷贝构造函数:

派生类的拷贝构造函数肯定是要考虑一下基类的,不然的基类就默认用标准构造函数生成了。

一般可以直接用upcasting解决这个问题,假设基类已经写了拷贝构造函数,那么派生类只需要:

Child(const Child& c) : Parent{c}, i{c.i}, m{c.m} {
}

上面的m是Child中的内嵌对象。

继承中的移动构造函数:

同理:

Child(Child&& c) : Parent{move(c)}, i{c.i}, m{move(c.m)} {
    cout << "Child(Child&&)\n";
}

final:

final修饰一个类时,该类不能被继承

class A final {
};

final修饰虚成员函数:
让其不能被继承:

virtual void f() final {

}