C++ 总结 (二、面向对象)
本文总结面向对象部分。
Class 类
类
是由结构体扩展而来,内部成员包含 实例变量
和 函数
对象
是类实例,类是它的类型,对象是变量。
类成员
(实例变量和函数)有三种访问权限: private
、protected
、public
- private - 仅仅类内部可以访问(类成员默认访问权限)
- protected - 仅仅类内部和其子类可以访问
- public - 任何对象可见的地方都可以访问
简单来说,定义和声明一个类与变量 class Rectangle rect 格式如下:
class Rectangle {
int width, height; // 默认private 访问权限
public:
void set_values (int,int);
int area (void);
} rect;
如上,类中的方法可以只声明,具体实现放到外部,外部写实现可使用域操作符实现。完整实现如下:
// classes example
#include <iostream>
using namespace std;
class Rectangle {
int width, height;
public:
void set_values (int,int);
int area() {return width*height;}
};
// 在外部实现类中的 set_values 函数
void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}
int main () {
Rectangle rect;
rect.set_values (3,4);
cout << "area: " << rect.area();
return 0;
}
// -----------------
area 12
构造器(类初始化器)
想一下,上面例子中如果没有调用 rect.set_values (3,4);
直接获取 area
会怎样?
构造器是类进行初始化的一个函数。它只会在初始化类对象时调用一次,它没有返回值,仅仅是用来初始化对象。构造器也是类的一个成员函数,它会在创建对象的时候自动调用,无法被显示调用。构造器函数名称同类名一样,且无任何返回值(即使是void)
// example: class constructor
#include <iostream>
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle (int,int); // 构造器函数
int area () {return (width*height);}
};
Rectangle::Rectangle (int a, int b) {
width = a;
height = b;
}
int main () {
Rectangle rect (3,4);
Rectangle rectb (5,6);
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() << endl;
return 0;
}
// 注意,此例中么有 set_values 函数,但是有一个相同效果的构造器函数。
构造器重载
构造器函数和普通函数一样可以构成重载(参数数量不同/参数类型不同)。
默认构造器: 与类名同名函数, 无参数,无返回值。其特殊在于: 当类对象声明的时会自动调用默认初始化器
// 对象声明就会调用默认初始化器
Rectangle rectb; // ok, default constructor called
// 加括号是声明一个无参函数的语法(返回值是Rectangle类型)
Rectangle rectc(); // oops, default constructor NOT called
统一初始化
初始化类对象有几种方式:初始化器方式,统一初始化方式、赋值声明方式等
如果初始化器只有一个参数,初始化可以调用变量赋值的方式,语法如下
class_name object_name = initialization_value;
在 C++ 中引入了统一初始化方式,其与函数式初始化相似,区别在于使用的是花括号{},而不是小括号()。在变量名与花括号之间可以可选使用 = 。语法如下
class_name object_name { value, value, value, ... }
class_name object_name = { value, value, value, ... } // 有无等号均可
一个完整的类初始化DEMO,包含四种初始化方式
// classes and uniform initialization
#include <iostream>
using namespace std;
class Circle {
double radius;
public:
Circle(double r) { radius = r; }
double circum() {return 2*radius*3.14159265;}
};
int main () {
Circle foo (10.0); // functional form
Circle bar = 20.0; // assignment init.
Circle baz {30.0}; // uniform init.
Circle qux = {40.0}; // POD-like
cout << "foo's circumference: " << foo.circum() << '\n';
return 0;
}
// --------------
foo's circumference: 62.8319
构造器中的成员初始化
构造器初始化 class 其他成员有几种方式,直接赋值成员变量 或者 使用初始化列表。
class Rectangle {
int width,height;
public:
Rectangle(int,int);
int area() {return width*height;}
};
// 方式1: 直接操作class 内部成员
Rectangle::Rectangle (int x, int y) { width=x; height=y; }
// 方式2: 使用 : 在参数与方法体 之间创建初始化列表
Rectangle::Rectangle (int x, int y) : width(x) { height=y; }
// 方式3: 同2
Rectangle::Rectangle (int x, int y) : width(x), height(y) { }
如果成员是基本类型,是否使用初始化列表的方式没有区别,因为基本类型无默认构造器
如果成员是class
类型,必须在初始化列表中调用初始化,否则它会自动调用默认构造器。
成员为 class 的初始化 Demo
// member initialization
#include <iostream>
using namespace std;
class Circle {
double radius;
public:
Circle(double r) : radius(r) { }
double area() {return radius*radius*3.14159265;}
};
class Cylinder {
Circle base;
double height;
public:
Cylinder(double r, double h) : base (r), height(h) {}
double volume() {return base.area() * height;}
};
int main () {
Cylinder foo (10,20);
cout << "foo's volume: " << foo.volume() << '\n';
return 0;
}
// -----------
foo's volume: 6283.19
// 注意 - 初始化方式可以使用同一初始化
Cylinder::Cylinder (double r, double h) : base{r}, height{h} { }
使用指针指向类
使用 class 定义的新类型可以和其他基本类型一样被使用,指针同样可以指向类对象的地址,指针访问对象内部成员使用箭头操作符(->)。
// pointer to classes example
#include <iostream>
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle(int x, int y) : width(x), height(y) {}
int area(void) { return width * height; }
};
int main() {
Rectangle obj (3, 4);
Rectangle * foo, * bar, * baz;
foo = &obj;
bar = new Rectangle (5, 6);
baz = new Rectangle[2] { {2,5}, {3,6} };
cout << "obj's area: " << obj.area() << '\n';
cout << "*foo's area: " << foo->area() << '\n';
cout << "*bar's area: " << bar->area() << '\n';
cout << "baz[0]'s area:" << baz[0].area() << '\n';
cout << "baz[1]'s area:" << baz[1].area() << '\n';
delete bar;
delete[] baz;
return 0;
}
几种运算符说明
expression | can be read as |
---|---|
*x | pointed to by x |
&x | address of x |
x.y | member y of object x |
x->y | member y of object pointed to by x |
(*x).y | member y of object pointed to by x (equivalent to the previous one) |
x[0] | first object pointed to by x |
x[1] | second object pointed to by x |
x[n] | (n+1)th object pointed to by x |
使用 struct 和 union 定义 class
类可以使用 struct 和 union 定义。
struct 定义类: 其成员变量默认访问权限为 public, class 默认为 private
union 定义类: 内部只能存储一个成员变量,访问权限为public,也可以有成员函数。
操作符重载
操作符本质上是系统内建的函数,C++ 中可以对常用操作符进行重载,来实现自定义的操作。格式
type operator sign (parameters) { /*... body ...*/ }
例如: 笛卡尔向量具有加法操作,使用 C++ 实现如下
// overloading operators example
#include <iostream>
using namespace std;
class CVector {
public:
int x,y;
CVector () {}; // 默认初始化器
CVector (int a,int b) : x(a), y(b) {} // 指定初始化器
CVector operator + (const CVector&); // 重写 + 运算符函数声明
};
CVector CVector::operator+ (const CVector& param) {
CVector temp;
temp.x = x + param.x;
temp.y = y + param.y;
return temp;
}
int main () {
CVector foo (3,1);
CVector bar (1,2);
CVector result;
result = foo + bar;
cout << result.x << ',' << result.y << '\n';
return 0;
}
// ----------------
4,3
// 注意 - 操作运算符重写后,显隐式调用均可
c = a + b;
c = a.operator+ (b)
重写操作符两种形式: 重写为成员函数(上)、重写为非成员函数(下)
// non-member operator overloads
#include <iostream>
using namespace std;
class CVector {
public:
int x,y;
CVector () {}
CVector (int a, int b) : x(a), y(b) {}
};
CVector operator+ (const CVector& lhs, const CVector& rhs) {
CVector temp;
temp.x = lhs.x + rhs.x;
temp.y = lhs.y + rhs.y;
return temp;
}
int main () {
CVector foo (3,1);
CVector bar (1,2);
CVector result;
result = foo + bar;
cout << result.x << ',' << result.y << '\n';
return 0;
}
this 关键字
this 关键字用来指示调用函数的当前对象的地址(当前对象的指针)。
// example on this
#include <iostream>
using namespace std;
class Dummy {
public:
bool isitme (Dummy& param);
};
bool Dummy::isitme (Dummy& param)
{
if (¶m == this) return true;
else return false;
}
int main () {
Dummy a;
Dummy* b = &a;
if ( b->isitme(a) )
cout << "yes, &a is b\n";
return 0;
}
this 也常用于通过引用返回对象的赋值运算函数中,如下(重写了笛卡尔向量的赋值操作)
CVector& CVector::operator= (const CVector& param)
{
x=param.x;
y=param.y;
return *this;
}
事实上,此 operator= 函数已经非常接近系统隐式生成的 operator= 代码了。
类中的静态成员
class 可以包含静态成员(成员变量 、函数)。关键字: static
静态成员变量全局只有一份内存,就像类的变量,可以直接用类名访问,所有的类对象访问其静态变量都是同一份内存。正是由于静态变量全局只有一份内存,所以其不能在class内部进行初始化,防止在创建新对象的时候多次初始化赋值。需要在class外部初始化一次即可。
// static members in classes
#include <iostream>
using namespace std;
class Dummy {
public:
static int n;
Dummy () { n++; };
};
int Dummy::n=0; // 静态变量,在class 初始化一次即可
int main () {
Dummy a;
Dummy b[5];
cout << a.n << '\n';
Dummy * c = new Dummy;
cout << Dummy::n << '\n';
delete c;
return 0;
}
// -------------,事实证明每次构造新对象,内部全局变量都是同一份内存
6
7
静态函数: 同样可以看做是外部函数,其调用方式:直接用类名调用,函数内部不能访问类实例变量,也不能使用功能this指针。
常量类型的成员函数
当一个类的对象被声明为常量: const MyClass myobject;
该对象就可以当做一个只读对象,对于其所有成员(实例变量/函数)都会被限制成只读。但是类的构造器函数是正常调用的。
// constructor on const object
#include <iostream>
using namespace std;
class MyClass {
public:
int x;
MyClass(int val) : x(val) {}
int get() {return x;}
};
int main() {
const MyClass foo(10);
// foo.x = 20; // not valid: x cannot be modified
cout << foo.x << '\n'; // ok: data member x can be read
return 0;
}
// ------------
10
被修饰为常量的对象,可以调用的函数,也只能是被标记了常量函数的。格式为在函数名之后,方法体之前添加 const 修饰符,举例如下:
int get() const {return x;} // const member function
const int& get() {return x;} // member function returning a const&
const int& get() const {return x;} // const member function returning a const&
常量对象: 我们很少这样定义,主要使用场景为函数的参数
常量函数: 函数是否为常量会被重载为不同函数,系统会根据对象是否为常量访问到正确的函数
// overloading members on constness
#include <iostream>
using namespace std;
class MyClass {
int x;
public:
MyClass(int val) : x(val) {}
const int& get() const {return x;}
int& get() {return x;}
};
int main() {
MyClass foo (10);
const MyClass bar (20);
foo.get() = 15; // ok: get() returns int&
// bar.get() = 25; // not valid: get() returns const int&
cout << foo.get() << '\n';
cout << bar.get() << '\n';
return 0;
}
// ----------------
15
20
类模板 - Class templates
同函数模板,类也可以使用模板,模板类允许类内部使用模板参数,模板格式template <class T>
;
模板类的成员函数,如果在类外部实现,那么函数也需要声明template <...>
前缀。
// class templates
#include <iostream>
using namespace std;
template <class T>
class mypair {
T a, b;
public:
mypair (T first, T second)
{a=first; b=second;}
T getmax ();
};
template <class T>
T mypair<T>::getmax () // 注意函数名中<T>,它标明了模板函数和模板类的参数。
{
T retval;
retval = a>b? a : b;
return retval;
}
int main () {
mypair <int> myobject (100, 75);
cout << myobject.getmax();
return 0;
}
// -----------
100
如上: 使用模板类保存一对 T 类型元素,构造函数定义在内部,外部提供了一个获取最大值的函数,外部实现需要添加 template <class T>
函数前缀。
专业化模板类
对于模板函数,如果传入某种特定类型,实现了特定处理,这就叫做专业化模板类。同通用模板区别如下
template <class T> class mycontainer { ... }; // 通用模板
template <> class mycontainer <char> { ... }; // 专业模板
注意: 两种模板函数,没有继承关系. 如下代码中是完全两个类。
// template specialization
#include <iostream>
using namespace std;
// class template:
template <class T>
class mycontainer {
T element;
public:
mycontainer (T arg) {element=arg;}
T increase () {return ++element;}
};
// class template specialization:
template <>
class mycontainer <char> {
char element;
public:
mycontainer (char arg) {element=arg;}
char uppercase ()
{
if ((element>='a')&&(element<='z'))
element+='A'-'a';
return element;
}
};
int main () {
mycontainer<int> myint (7);
mycontainer<char> mychar ('j');
cout << myint.increase() << endl;
cout << mychar.uppercase() << endl;
return 0;
}
// ------
8
J
类的特殊成员函数
Class 中有六种特殊成员函数,如下
成员函数 | 形式: C 为类 | 隐式生成条件 | 默认操作 |
---|---|---|---|
默认构造器: Default constructor | C::C(); | 无其他构造器 | 无 |
析构函数: Destructor | C::~C(); | 析构函数 | 无 |
拷贝构造器: Copy constructor | C::C (const C&); | 无移动构造和无移动赋值 | copy所有成员 |
拷贝赋值: Copy assignment | C& operator= (const C&); | 无移动构造和无移动赋值 | copy所有成员 |
移动构造器: Move constructor | C::C (C&&); | 无析构函数、无复制构造、无复制和移动赋值(即仅有构造函数) | 移动所有成员 |
移动赋值: Move assignment | C& operator= (C&&); | 无析构函数、无复制构造、无复制和移动赋值(即仅有构造函数) | 移动所有成员 |
默认构造器 与 自定义构造器
如果定义一个类,类中没有声明构造器,编译器会隐式生成一个默认构造器,默认构造器无参数。
如果有自定义的构造器,则不会隐式生成默认构造器,所以通常如果写构造器就要写两种:默认和自定义
析构函数
析构函数: 内部主要释放对象管理内存的成员。如对象内部有其他对象。
拷贝构造器
指的是使用该类对象作为参数的构造函数。 如B b = a;
拷贝赋值
重载等号 = , 使用该类对象赋值给已定义对象。 如b = a;
移动构造器
构造对象使用函数返回值的方式,如 B b = B();
B() 是一个返回值为 B& 的函数
移动赋值函数
移动赋值,类似拷贝赋值、右侧参数为未使用对象
// destructors
#include <iostream>
#include <string>
using namespace std;
class Example4 {
string* ptr;
public:
// constructors:
Example4() : ptr(new string) {}
Example4 (const string& str) : ptr(new string(str)) {}
// destructor:
~Example4 () {delete ptr;}
// copy constructor: 深拷贝,隐式生成的浅拷贝
Example4 (const Example4& x) : ptr(new string(x.content())) {}
// copy assignment:
Example4& operator= (const Example4& x) {
*ptr = x.content();
return *this;
}
// move constructor
Example4 (Example4&& x) : ptr(x.ptr) {x.ptr=nullptr;}
// move assignment
Example4& operator= (Example4&& x) {
delete ptr;
ptr = x.ptr;
x.ptr=nullptr;
return *this;
}
// access content:
const string& content() const {return *ptr;}
};
int main () {
Example4 foo;
Example4 bar ("Example");
return 0;
}
看一下平常使用的赋值其本质的操作如下:
MyClass fn(); // function returning a MyClass object
MyClass foo; // default constructor
MyClass bar = foo; // copy constructor
MyClass baz = fn(); // move constructor
foo = bar; // copy assignment
baz = MyClass(); // move assignment
default 和 delete 关键字
C++ 有 default 和 delete 关键字来告诉编译器使用还是删除上述特殊函数。语法: 在函数声明后面添加 default / delete 关键字即可
struct Test {
// 使用默认构造函数
Test() = default;
// 删除复制赋值运算符
Test& operator=(const Test& test) = delete;
// 使用默认析构函数
~Test() = default;
};
朋友关系 和 继承关系
Class 中成员默认访问级别为 private。 意味着在此类之外无法访问类成员。
朋友: 是用 friend
关键字声明的类或函数。朋友函数能在类外访问其成员。但是朋友的朋友不具备此能力。
原则就是: 在类中定义的 friend 函数/类 可以在外部访问类内部成员。谁被friend修饰,谁有此权限
朋友函数示例:
// friend functions
#include <iostream>
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle() {}
Rectangle (int x, int y) : width(x), height(y) {}
int area() {return width * height;}
friend Rectangle duplicate (const Rectangle&);
};
// 此函数为朋友函数,可以访问参数类内部成员
Rectangle duplicate (const Rectangle& param)
{
Rectangle res;
res.width = param.width*2;
res.height = param.height*2;
return res;
}
int main () {
Rectangle foo;
Rectangle bar (2,3);
foo = duplicate (bar);
cout << foo.area() << '\n';
return 0;
}
朋友类示例:
// friend class
#include <iostream>
using namespace std;
// 声明存在该类,后面都可以使用该类名
class Square;
class Rectangle {
int width, height;
public:
int area ()
{return (width * height);}
void convert (Square a);
};
class Square {
// Rectangle 被声明为朋友类,代表 Rectangle 内部可以访问当前类的私有成员
friend class Rectangle;
private:
int side;
public:
Square (int a) : side(a) {}
};
void Rectangle::convert (Square a) {
width = a.side;
height = a.side;
}
int main () {
Rectangle rect;
Square sqr (4);
rect.convert(sqr);
cout << rect.area();
return 0;
}
继承关系
类之间可以继承,形成基类和子类的关系。
子类会继承父类的所有的成员。
子类可以自己定义自己新的成员、可以自定义实现父类的函数。
语法:
class derived_class_name: public base_class_name
{ /*...*/ };
DEMO
// derived classes
#include <iostream>
using namespace std;
class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b;}
};
class Rectangle: public Polygon {
public:
int area ()
{ return width * height; }
};
class Triangle: public Polygon {
public:
int area ()
{ return width * height / 2; }
};
int main () {
Rectangle rect;
Triangle trgl;
rect.set_values (4,5);
trgl.set_values (4,5);
cout << rect.area() << '\n';
cout << trgl.area() << '\n';
return 0;
}
// --------
20
10
Rectangle 和 Triangle 分别继承基类 Polygon. 其内部成员 width, height, set_values 也都继承了。
继承语法中冒号后的 public 限制了继承时候对父类成员的访问级别。建议使用public,不同关键字对子类继承的成员访问权限如下:
Access | public | protected | private |
---|---|---|---|
基类的对象内部 | yes | yes | Yes |
子类对象内部 | yes | yes | no |
外界(其他函数、类) | yes | no | no |
继承语法三个关键字: public、protected、private。作用如下
public : 子类完整按照父类的成员本身的访问继承
protected : 子类继承的成员访问级别为 public 的会降级为 protected
private : 子类继承的成员都会降级为 private
继承关系继承不到的
子类虽然能继承父类成员,但是以下几种情况是无法继承的
- 父类的构造函数和析构函数
- 其分配运算符成员(operator=)
- 父类内的朋友成员
- 父类内部 private 访问属性的成员
子类的构造方法会调用父类的默认构造器。也可以通过以下语法调用指定的父类构造器。
指定调用父类构造函数derived_constructor_name (parameters) : base_constructor_name (parameters) {...}
// constructors and derived classes
#include <iostream>
using namespace std;
class Mother {
public:
Mother ()
{ cout << "Mother: no parameters\n"; }
Mother (int a)
{ cout << "Mother: int parameter\n"; }
};
class Daughter : public Mother {
public:
// 子类构造函数没有指定特定父类构造器: 调用父类默认构造器
Daughter (int a)
{ cout << "Daughter: int parameter\n\n"; }
};
class Son : public Mother {
public:
// 指定父类构造器: 会调用父类指定构造器
Son (int a) : Mother (a)
{ cout << "Son: int parameter\n\n"; }
};
int main () {
Daughter kelly(0);
Son bud(0);
return 0;
}
// -----------
Mother: no parameters
Daughter: int parameter
Mother: int parameter
Son: int parameter
类的多继承
C++ 中类的继承是可以多继承: 即同时有多个父类。语法即在继承的冒号之后用分号列举父类列表。一个完整的例子如下:
// multiple inheritance
#include <iostream>
using namespace std;
class Polygon {
protected:
int width, height;
public:
Polygon (int a, int b) : width(a), height(b) {}
};
class Output {
public:
static void print (int i);
};
void Output::print (int i) {
cout << i << '\n';
}
class Rectangle: public Polygon, public Output {
public:
Rectangle (int a, int b) : Polygon(a,b) {}
int area ()
{ return width*height; }
};
class Triangle: public Polygon, public Output {
public:
Triangle (int a, int b) : Polygon(a,b) {}
int area ()
{ return width*height/2; }
};
int main () {
Rectangle rect (4,5);
Triangle trgl (4,5);
rect.print (rect.area());
Triangle::print (trgl.area());
return 0;
}
// -----------
20
10
C++ 中类的多态
多态: 即父类指针指向子类对象,指针调用对象方法的时候、编译器能找到正确的对象类型并正确调用到真实子类的对象方法。
C++ 中的多态有一些特殊的关键字: virtual。
基类定义的成员是通用成员,子类继承后一般会进行功能扩展。指向基类的指针无法直接访问子类成员。为了使基类更加通用,可以在基类中使用 virtual 声明子类的成员。
virtual 作用即定义基类中可以访问的子类内容。
// virtual members
#include <iostream>
using namespace std;
class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area ()
{ return 0; }
};
class Rectangle: public Polygon {
public:
int area ()
{ return width * height; }
};
class Triangle: public Polygon {
public:
int area ()
{ return (width * height / 2); }
};
int main () {
Rectangle rect;
Triangle trgl;
Polygon poly;
Polygon * ppoly1 = ▭
Polygon * ppoly2 = &trgl;
Polygon * ppoly3 = &poly;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
ppoly3->set_values (4,5);
cout << ppoly1->area() << '\n';
cout << ppoly2->area() << '\n';
cout << ppoly3->area() << '\n';
return 0;
}
// -----------
20
10
0
如上: 基类中 area() 成员是 virtual 修饰的,所以指向基类指针可以直接调用此方法。否则 area() 方法只能在子类指针的访问。
抽象基类
C++ 中提供抽象类的概念,其只能做为基类。类似上面示例,基类中提供 virtual 成员,语法为成员函数声明的定义直接 =0
;
抽象类无法声明自己对象,但是可以声明指向该类型的指针。
// abstract class CPolygon
class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
// 注意: 只要包含一个可见成员,该类就成为抽象类
virtual int area () =0;
// 抽象基类中,其他成员可以使用 this关键字访问可见成员。(系统会访问到真实子类成员)
void printarea()
{ cout << this->area() << '\n'; }
};
正是由于可见成员和抽象类构成了 C++ 的多态特性,下面是一个完整包含动态内存分配
、类构造初始化器
、多态
的示例
// dynamic allocation and polymorphism
#include <iostream>
using namespace std;
class Polygon {
protected:
int width, height;
public:
Polygon (int a, int b) : width(a), height(b) {}
virtual int area (void) =0;
void printarea()
{ cout << this->area() << '\n'; }
};
class Rectangle: public Polygon {
public:
Rectangle(int a,int b) : Polygon(a,b) {}
int area()
{ return width*height; }
};
class Triangle: public Polygon {
public:
Triangle(int a,int b) : Polygon(a,b) {}
int area()
{ return width*height/2; }
};
int main () {
Polygon * ppoly1 = new Rectangle (4,5); // 直接将子类对象赋值给父类指针(多态)、动态内存分配
Polygon * ppoly2 = new Triangle (4,5);
ppoly1->printarea();
ppoly2->printarea();
delete ppoly1;
delete ppoly2;
return 0;
}