面向对象编程是C++
引入的另一种思想,也成为了和C
语言之间最大的区别了。生活中或者实际问题中的对象经过抽象,就可以抽象地用类去定义对象以及对象所具有的方法。
认识类
类是对实际的对象进行抽象的结果,它类似于一个结构体Plus
。对象就是数据和方法的集合,面向对象的编程主要有下面几个要点。
抽象
封装
声明及使用类
类的声明以数据成员的方式描述数据部分,以及成员函数方法
的方式描述公有接口。而类的方法定义描述了如何实现成员函数。简单的说,类声明提供了类的蓝图,而方法实现则提供了细节。
类的声明
鼓励类的声明声明在头文件中,在编译过程中.h
文件要和其他文件链接,所有用到其中类的cpp
文件都需要加上inclucde ""
来引用这个文件中包含的信息,命名时常利用大驼峰命名。
#ifndef STUDENT_H
#define STUDENT_H
#include <string>
using namespace std;
// Class declared in c++ .h file.
class Student {
private:
string name;
double aver;
int age;
public:
void WriteData(string, int);
void ShowData();
void Add(double, double);
};
#endif
声明类时用#ifndef
和#define
和#endif
是一种编译指令。类的声明中和函数有区别,尽量遵循类的声明在一个头文件中,类的实现在一个和头文件同名的源文件中,而类的使用则是在程序的主文件中。
#include "student.h"
#include <iostream>
// To write the path of the header file to reference.
void Student::WriteData(string str, int a) {
this->name = str;
this->age = a;
}
void Student::ShowData() {
cout << "Name: " << this->name << endl;
cout << "Age: " << this->age << endl;
}
void Student::Add(double a, double b) { this->aver = (a + b) / 2; }
在定义中使用了this
指针是用来指向这个类本身的属性的。
类中分为私有和公有
私有不能被外界直接调用,而公有部分可以被直接调用
在自身的定义中
this
可以指向类本身的私有部分和公有部分
类的特点
接口
接口是一个共享框架,对于类的接口,public
实用类的程序,交互是由类对象完成的,通过这些接口可以在接口设计者的许可下访问或者操作其中的私有部分或公有部分。如果有这样的接口,即使根据规则无法直接使用类中的元素,但是可以通过接口的一些办法实现。
数据隐藏
通过划定private
和public
可以防止直接访问数据,使得数据只能被被授权的函数访问,还让开发者无需了解数据是怎样被表示的,把实现细节分离出来,这样在维护程序中更安全便利。
类的使用
Student Wong;
// Declare like a normal variable, sometimes with a constructor.
Wong.WriteData("Wong", 18);
Wong.Add(97.5, 99);
Wong.ShowData();
类的使用更像是结构体的使用只不过类一般具有方法供给使用,在使用时加入要求的参数就可以从外部访问或者操作类,面向对象之前先要有对象。
构造函数和析构函数
一般类是由默认隐式存在的构造函数和析构函数。构造函数
是在类对象生成时自动执行的函数,析构函数
是在类对象消失时自动执行的函数。由于这两类函数是默认的,可以将他们显式的表示出来,那么在运行时就会自动的使用这些函数,还可以根据他们的参数进行重载。
- 声明构造函数和析构函数
#ifndef PERSON_H
#define PERSON_H
class Person {
private:
double money;
int age;
public:
Person(double, int);
~Person();
};
#endif
实现构造函数和析构函数
Person::Person(double a, int b) {
money = a;
age = b;
}
Person::~Person() { cout << "Deleted!" << endl; }
使用构造函数和析构函数
使用构造函数和析构函数都是自动调用的如果有参数可以在类对象声明时后加括号传递参数。
Person a(9750.00, 36);
// Constructors can also have multiple overloads.
析构函数有些特别他是在对象的存在周期的结束调用的,不需要显式调用。
类的细节
this 指针
有些方法会涉及到两个对象或者更多的对象,那么这些对象的类名是相同的。有一种只能且默认指向该方法调用者本身的this
指针。
每个成员函数包括构造函数和析构函数都有一个
this
指针如果需要,
*this
代表整个对象,且const
可以防止修改对象
const
对象的实质就是使this
成为常量这样就不能移动地址且不能修改值
列表初始化
一种构造函数的特殊初始化方法,可以直接列表对应的表示传入的值和要接受的内部元素。
class Person {
private:
int age;
int money;
public:
Person();
Person(int a, int b) : age(a), money(b) {}
// code...
};
static 类成员
static
修饰的变量先于对象存在,是所有对象都可以共享的成员,必须要在类外初始化并且先于对象存在。
class A {
public:
static int num;
static void Add() { num++; }
};
// Initialization must be done outside of class.
int A::num = 0;
静态成员数据是在该类的所有对象之间是共用的。还有静态成员函数,普通的成员函数具有this
指针,如果是静态成员函数不隶属于任何类对象而属于这个类没有this
指针,所以它无法修改非静态的成员变量。
A a;
a.Add();
A::Add();
// You can call this static member function that does not belong to any object
// through a field operator.
解释友元
通常,只有类成员函数才能访问类中元素,但是这种限制太严格。C++
提供了另一种形式的访问权限就是友元。友元还可以解决某些重载运算符的第一个操作数的顺序问题,使得不拥有重载运算符定义的基本类型也可以享受重载后运算的扩展。
- 友元函数
- 友元类
友元函数可以直接访问私有成员,创建友元的先要在声明函数时加关键字friend
,类中声明的友元函数虽然是普通函数但是却有着访问类成员的权限。
声明友元函数
class Integer {
private:
int value;
public:
Integer() {}
Integer(int a) : value(a) {}
friend const Integer operator+(int, const Integer &);
}
实现友元函数
const Integer operator+(int a, const Integer &b) {
return Integer(a + b.value)
// Even if the overload is not a member function, you can still use private
// members directly.
}
因为友元不是成员函数,所以友元没有this
指针。注意调用的时候是直接当作普通函数调用,区别是它能访问类中的元素。
声明友元类
这样友元类可以访问他所对应的类的private
成员。
// Student.h
class Student {
// Class Teacher could access the private member in student class.
friend class Teacher;
private:
int grade;
public:
Student() {}
Student(int);
~Student() {}
};
继承和派生
在C++
提供了更高级的代码重用功能,在有现有类库的情况下,可以直接用类的继承一个类的功能,以实现代码的重用。
类的派生
派生类的功能
可以在已有的类基础上添加功能
可以给类添加数据
可以修改类方法的行为
// Derived classes need to indicate the name of the class to inherit.
class A : public B {
// code...
}
其中public
为继承方法,决定了基类的成员在派生类中所处的权限,这些被public
继承的自然就会成为派生类中的public
成员。
派生类需要什么
派生类需要自己的构造函数,十分重要
派生类可以根据需要添加额外的成员和成员函数
在父类中存在几种权限类型,在子类中对应的的使用权限也不同。这里说下protected
权限,这种权限不继承的时候相当于私有成员,而在继承的时候可以被继承。
类的继承
继承方式 | 基类 public | 基类 protected | 基类 private | 继承引起的变化 |
---|---|---|---|---|
public | 仍为 public | 仍为 protected | 不可见 | 访问属性不变 |
protected | 变为 protected | 变为 protected | 不可见 | 私有成员变为保护 |
private | 变为 private | 变为 private | 不可见 | 非私有成员变为私有 |
由上表可以看出,基类的private
成员是铁定不能被派生类继承使用的。所以在继承类的时候应该十分注意成员的继承权限,防止多次继承之后的成员的继承关系混乱。
- 构造派生类
在创建派生类对象时,程序首先会调用基类的构造函数,然后再去调用派生类的构造函数,所以派生类的构造函数默认会含有一个基类的构造函数。当然调用基类的哪一种构造函数也是可以进行指定的。
- 析构派生类
在派生类对象销毁时,程序会首先调用派生类的析构函数,再调用基类的析构函数。
以上提到定义使用何种基类的构造函数,需要使用成员初始化列表
。
#include "student.h"
#include <iostream>
#include <string>
using namespace std;
Student::Student(string c, int a, int b) {
name = c;
age = a;
grade = b;
}
void Student::ShowData() { cout << age << grade; }
继承构造函数
派生类的构造函数可以显式的指出调用那种基类构造函数,即“派生类构造函数:基类构造函数”。
#include <string>
#include <iostream>
#include "student.h"
#include "studentplus.h"
// Here some parameters are shared so that it can be initialized with same value.
StudentPlus::StudentPlus(string a,string b,int c,int d):Student(b, c, d){
address = a;
}
void StudentPlus::ShowPlus(){
cout << address;
}
派生类的使用
派生类的使用要记住把基类和派生类头文件以引用两个类的所有信息,前面提到的成员列表初始化
,我们在新建派生类对象的时候需要注意构造函数的参数列表的具体方案。
// code...
string Address,Name;
int Age,Grade;
StudentPlus wong(Address,Name,Age,Grade);
wong.ShowData();
wong.ShowPlus();
// It can use the public methods of both base and derived classes.
protected 和 private 的区别
对于类外的成员,这两个都是无法被访问的。而对于其派生类而言,protected
可以继承的,private
在什么情况都是无法被其他类访问或继承的。
深浅拷贝
在类的拷贝过程中有两种拷贝,一种是靠编译器普通的拷贝,一种是靠拷贝构造函数自己定义的拷贝。主要用于解决拥有堆内存的类内指针重复释放或者同时指向某一内存空间的问题。拷贝构造函数是一种特殊的构造函数,只有在构造对象的时候才会被调用。
浅拷贝
class Person {
private:
int age;
public:
Person() {}
Person(int a) : age(a) {}
~Person() {}
}
浅拷贝主要靠的是默认的拷贝函数,值得注意的是这个拷贝函数是遵循值拷贝的规则,这样就有可能把指针的值拷贝后指向同一块内存空间,为了避免这种情况的发生就有了下面的深拷贝。
Person b(a);
// The compiler helps construct the copy constructor.
深拷贝
Person::Person(int a) { p = new int(a); }
// Heap memory, manually opened, manually released.
Person::~Person() {
delete p;
p = NULL;
}
- 注意
如果在直接对带有动态内存的类进行浅拷贝,但是其中维护堆内存的指针指向了同一地址,会导致同一内存地址被释放两次。所以我们要用深拷贝来避免两者同时只想统一地址。
class Person {
private:
int *p;
public:
Person(int);
Person(const Person &);
~Person();
}
Person::Person(int a) { p = new int(a); }
Person::Person(const Person &b) {
p = new int(*b.p);
// De-reference the pointer to get the value of the pointer, so that the
// copied class reapplys for a memory chunk of the same value, copying only
// the value, not the address.
}
Person::~Person() {
delete p;
p = NULL;
}
继承中的指针运用
派生类的指针不能够指向基类,而基类的指针可以指向派生类,即不能将指针往上追溯。
// Yes!
StudentPlus s();
Student *p = &s;
// No!
Student s();
StudentPlus *p = &s;
静态联编
虽然派生类对象或者指针可以赋值给基类的对象或者指针,但是函数的调用关系已经确定了,基类的对象和指针调用的函数都是基类的函数不会因为赋值而发生改变。
动态联编
即为多态,当想用基类对象或者指针来调用赋值过后的的新的函数,这时候就要把函数定义为虚函数,这样在赋值的时候就可以将函数指针的指向重置,来指向新的函数。当然这样的函数继承之后还是虚函数,一直保持这种“虚构”的状态。指针或引用赋值的时候才能覆盖虚函数,对于对象的直接赋值只会把对象的数据进行赋值,而不会影响到对象的函数表,指针和引用则可以深层次的影响虚函数表。