定义类时,class
和struct
关键字唯一的区别在于默认访问权限。
构造函数
默认构造函数
只有当类没有声明任何构造函数的情况下,才会自动生成默认构造函数,否则需要手动构造。
但是如果构造函数的参数都设置了默认值,那么相当于定义了默认构造函数
class Base {
public:
int i;
int j;
// A不会报错,因为相当于有无参默认构造函数了
Base(int v = 11) : i(v) {}
// A报错,因为声明了构造函数后,不会自动生成默认构造函数
Base(int v) : i(v) {}
};
class A {
public:
Base base;
int v;
// base会调用Base的默认构造函数
A(int i) : v(i) {}
};
int main() {
A a(1);
cout<<a.base.i<<endl;
}
=default
因为若定义含参构造函数,那么我们需要有默认构造函数,但是这个时候不会再自动生成(原因如上)。所以我们使用BaseClass()=default
来让编译器自动生成默认构造函数。
类外构造函数
可以在类里面声明构造函数,类外实现构造函数
class Base {
public:
int v = 1;
string s;
Base();
};
Base::Base(){
v = 11;
s = "??";
}
构造函数初始化列表
class Base {
private:
int val;
string s;
const int a;
int &ri;
const int b = 1;
public:
// const对象和引用必须被显式初始化
Base(int v, string S) : val(v), s(S), a(1), ri(val) {}
// 与上面效果等价,但是没有使用列表初始化
Base(int v, string S){
val = v;
s = S;
// error:const对象和引用只可以被显式初始化
a = 1;
ri = val;
}
};
注意:
使用列表初始化的话,可以直接显式的初始化成员,如上面第一个。
没有使用列表初始化,那么:在构造函数体之前,成员函数先执行默认初始化,然后在函数体内再执行赋值操作。
const对象和引用必须被显式初始化,构造函数一开始执行,初始化就完成了,所以只可以通过参数列表或者类成员默认值来初始化
成员初始化顺序
与在函数初始值列表中出现的顺序无关,因为其不限定初始化的具体执行顺序。其成员的初始化顺序与他们在类中定义的出现顺序一致。
class Base {
public:
int i;
int j;
// Base(int v) : j(v), i(j) {}
// cout值 1 0. 因为i定义在j之前
Base(int v) : i(v), j(i) {}
// cout值 1 1
};
int main() {
Base base(1);
cout<<base.j<<" "<<base.i;
}
委托构造函数,cpp11新标准
其实就是一个类的构造函数可以调用其他构造函数
隐式类型转换
下面是例子,有对应的构造函数的时候,可以进行隐式转化。
class Base {
public:
int i;
string s;
Base() : s("NULL"), i(0) {}
Base(int v = 11) : i(v), s("NULL") {}
Base(string S = "NULL") : s(S), i(0) {}
Base(string S, int v) : s(S), i(v) {}
// 定义为void show(Base& p) const 的话就不可以隐式转换了
void show(Base p) const {
cout << s << "-" << i << " AND " << p.s << "-" << p.i << endl;
}
explicit Base(pair<int, int> p){
// ...
// 此构造函数不支持隐式转换,因为有explicit,除非用static_cast<Base>()
}
};
int main() {
string s = "HELLO";
Base base("HAHA");
base.show(s); // HAHA-0 AND HELLO-0
int p = 111;
base.show(p); // HAHA-0 AND NULL-111
// error,只允许一步类类型转换,下面需要先吧"Hello"转为string,再转为Base,所以错误
// base.show("Hello");
base.show(string("Hello"));
}
禁止隐式转换,关键字explicit
聚合类
类的静态成员
类的静态成员存在于任何对象之外,每个静态成员,每个类只有一个对应的静态成员对象,静态成员函数不与任何对象绑定,因而其没有this指针,不能声明为const。
但是使用类的静态成员时,我们可以有如下访问方式
// 假设cnt和plus()是静态变量和静态成员
int a = Base::cnt;
Base::plus();
base.plus();
Base &r = base;
r.plus();
Base*pp = &base;
pp->plus();
初始化
只有常量可以在类内初始化
class Base{
static const int cnt = 1;
static constexpr int cnt2 = 1;
static int v;
};
友元
类可
以允许其他类或者函数访问其非公成员:将其他类或者函数声明为其友元。
友元声明只能出现在类定义的内部。友元不是类的内部成员也不受它所在区域的访问控制级别限制。
友元没有传递性
class D{
// 先定义
void f();
};
class Base {
// 一般统一声明友元
friend ostream &printBase(ostream &os, Base &base);
// 友元类
friend class FB;
// 将某个类的函数设为友元
// 要求:
// 首先定义D类,其中声明f函数,但是不能定义它。在f函数使用Base的成员之前必须先声明Base
// 定义Base,并声明友元
// 定义D::f,这时候才可以访问Base的成员
friend void D::f();
private:
int v = 1;
string s;
public:
Base() : v(100), s("Hello World !") {}
};
class FB {
Base base;
public:
FB() = default;
FB(Base base1) : base(base1) {
cout << "Friend Class From Base!" << endl;
cout << base.v << " " << base.s << endl;
}
};
ostream &printBase(ostream &os, Base &base) {
os << base.s << " " << base.v << endl;
return os;
}
void D::f(){
// visit Base
}
int main() {
Base base;
printBase(cout, base);
FB fb(base);
}
类的其他feature
//
// Created by 13653 on 2022/4/20.
//
#include<iostream>
#include<vector>
using namespace std;
class Screen {
public:
using pos = string::size_type;
Screen() = default;
Screen(pos ht, pos wd, char c = ' ') : height(ht), width(wd), contents(ht * wd, c) {}
char get() const {
return contents[cur]; // 隐式内联,在类中定义的函数一般会隐式自动内联
}
// 重载了get
inline char get(pos ht, pos wd) const; // 显式内联
Screen &move(pos r, pos c); // 可以在之后设置为内联private:
// size_type 是无符号类型,可以表示任意string对象的长度
void some_member() const {
// mutable永远不会是const的
++access_ctr;
}
Screen &set(pos r, pos c, char v) {
contents[r * width + c] = v;
return *this;
}
Screen &set(char v) {
contents[cur] = v;
return *this;
}
// 基于const的重载
// Screen display(ostream& os) const{
// doDisplay(os);
// return *this;
// }
const Screen &display(ostream &os) const {
cout << "const 版本" << endl;
// const函数只能调用const函数
doDisplay(os);
return *this;
}
Screen &display(ostream &os) {
cout << "非 const 版本" << endl;
doDisplay(os);
return *this;
}
private:
pos height = 0, width = 0, cur = 0;
mutable pos access_ctr;
string contents;
void doDisplay(ostream &os) const {
os << contents << endl;
}
};
// 在函数定义处指定内联,别忘了加Screen:: 指定类名
inline Screen &Screen::move(pos r, pos c) {
pos row = r * width;
cur = row + c;
return *this;
}
// 在类的内部声明成inline
char Screen::get(pos ht, pos wd) const {
pos row = ht * width;
return contents[row + wd];
}
class Window_mgr {
private:
// 类内初始化只可以通过两种方式:
// I:使用等号,如 int a = 1;
// II: 花括号
vector<Screen> screens{Screen(2, 3, ' ')};
int a{3};
};
int main() {
Screen myScreen(2, 2, 'A');
const Screen blank(1, 1, 'B');
myScreen.display(cout).set('Q').display(cout);
blank.display(cout);
/*在类中我们可以基于const重载,因为有时为了链式调用,我们会返回类的引用对象
* 但是const函数不可以返回非const的引用对象。我们可以针对const来重载函数
* 因为非常量版的函数对于常量对象来说是不可引用的,所以一个常量对象只可以调用const成员函数
* 非常量对象可以调用const和非const函数,但是优先匹配非const函数
*
*/
}
类的前向声明
类可以先被声明但不被定义,这个时候他是不完全类型(因为不清楚其成员)。这个时候我们可以定义指向这个类型的指针或引用,也可以将其作为参数或者返回类型。
但是我们不可以创建其对象,因为它还没被定义,编译器无法为其分配空间等一系列操作。
class Node {
int v;
// 是可以的,因为是指针,这个时候不必初始化
Node *left, *right;
};
class Node2 {
int v;
// 不行,因为只有当类被全部完成后才算被定义,所以类不可以指向自己
// Node2 left, right;
};
// X有Y的指针,Y有X的对象
class Y;
class X {
Y *y;
};
class Y {
X x;
};