C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,是一种用户自定义的数据类型,它是一种封装了数据和函数的组合。类中的数据称为成员变量,函数称为成员函数。
class classname {
访问权限修饰符 public/private/protected
变量;
方法(){}
}
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。
Box Box1;
Box Box2;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// 成员函数声明
double get(void);
void set( double len, double bre, double hei );
};
// 成员函数定义
double Box::get(void)
{
return length * breadth * height;
}
void Box::set( double len, double bre, double hei)
{
length = len;
breadth = bre;
height = hei;
}
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
//成员函数可以在类内部定义
double getVolume(void)
{
return length * breadth * height;
}
};
Box myBox; // 创建一个对象
myBox.getVolume(); // 调用该对象的成员函数
公有成员在程序中类的外部是可访问的,可以不使用任何成员函数来设置和获取公有变量的值。
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的,默认情况下,类的所有成员都是私有的。
受保护的成员变量或函数与私有成员十分相似,但有一点不同,protected成员在子类中是可访问的。
继承中的特点
class B : public A{}
class B : protected A{}
class B : private A{}
类的构造函数是类的一种特殊的成员函数,它会每次创建类的新对象时执行。
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数
Line(double len); //带参构造函数
private:
double length;
};
//构造函数定义
Line::Line(void)
{
cout << "Object is being created" << endl;
}
//带参构造函数定义
Line::Line( double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}
方法一
Line::Line( double len): length(len)
{
cout << "Object is being created, length = " << len << endl;
}
方法二
Line::Line( double len)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。如果自己不提供,编译器会提供一个空实现的构造和析构。
//析构函数声明
~Line();
//析构函数定义
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。如果在类中没有定义拷贝构造函数,编译器会自行定义一个,如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
拷贝构造函数通常用于:
void test01() { Person p1(20); Person p2(p1); }
void doWork(Person p) { } void test02() { Person p; doWork(p); }
Person doWork2() { Person p1; return p1; }
classname (const classname &obj) {
// 构造函数的主体
}
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
深拷贝
在堆区重新申请空间,进行拷贝操作。
m_Height = new int(*p.m_Height)
浅拷贝
简单的赋值拷贝操作,带来的问题就是堆区内存重复释放。
m_Height = p.m_Height
类的友元函数是定义在类外部,但是有权访问类的所有私有成员和保护成员,尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
声明函数为一个类的友元,需要在类定义中在该函数前使用关键字friend。
class Building{
friend class GoodGay;
}
class GoodGay{}
class Building{
friend void GoodGay::visit();
}
class GoodGay{
void visit();
}
class Box
{
double width;
public:
double length;
friend void printWidth( Box box );
void setWidth( double wid );
};
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
内联函数是通常与类一起使用,如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用函数的地方,对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
如果想把一个函数定义为内联函数,则需要在函数名前放置关键字inline,在调用函数之前需要对函数进行定义。在类定义中定义的函数都是内联函数,即使没有使用inline说明。
inline int Max(int x, int y)
{
return (x > y)? x : y;
}
this指针是一个特殊的指针,指向当前对象的实例,每一个对象都能通过this指针来访问自己的地址,this是一个隐藏的指针,可以在类的成员函数中使用,它可以用来指向调用对象,当一个对象的成员函数被调用时,编译器会隐士地传递该对象的地址作为this指针,友元函数没有this指针,因为友元不是类的成员,只有成员函数才有this指针。
class MyClass {
private:
int value;
public:
void setValue(int value) {
this->value = value;
}
void printValue() {
std::cout << "Value: " << this->value << std::endl;
}
};
Person& PersonAddAge(Person& p) {
this->m_Age += p.m_Age;
return *this;
}
Person p1(10);
Person p2(10);
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); //40
Person(int age) {
//this指针指向被调用的成员函数所属的对象
this->m_Age = age;
}
一个指向类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 -> ,就像访问指向结构的指针一样。
Box *ptrBox;
// 保存第一个对象的地址
ptrBox = &Box1;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box1: " << ptrBox->Volume() << endl;
使用static关键字来把成员定义为静态的,当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。静态成员在类的所有对象中是共享的,如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符**:*来重新声明静态变量从而对它进行初始化。
class Box
{
public:
static int objectCount;
}
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
静态成员函数与普通成员函数的区别:
对已有的运算符重新定义,赋予其另一种功能,以适应不同的数据类型。
成员函数重载+号
Person operator+(Person& p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
全局函数重载+号
Person operator+(Person& p1, Person& p2) {
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
全局函数重载左移运算符<<
ostream & operator<<(ostream &cout, Person &p) {
cout << "m_A" << p.m_A << "m_B" << p.m_B;
return cout;
}
前置递增
//成员函数重载前置++运算符 返回引用是为了一直对一个数进行递增
MyInteger& operator++() {
m_Num++;
return *this;
}
后置递增
//成员函数重载后置++运算符 int代表站位参数,用于区分前置和后置 必须是int
MyInteger operator++(int) {
MyInteger temp = *this;
m_Num++;
return temp;
}
<<重载
ostream& operator<<(ostream& cout, MyInteger myint) {
cout << myint.m_Num;
return cout;
}
//重载赋值运算符
Person1& operator=(Person1& p) {
//先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != NULL) {
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);
return *this;
}
//重载关系运算符==
bool operator==(Person2& p) {
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
return true;
}
return false;
}
class Myprint {
public:
void operator()(string text) {
cout << text << endl;
}
};
void test07() {
Myprint myFunc;
myFunc("hello world");
}
继承的好处就是减少重复代码,
class 子类 : 继承方式 父类
// 基类
class Animal {
// eat() 函数
// sleep() 函数
};
//派生类
class Dog : public Animal {
// bark() 函数
};
class A {
public :
int a;
protected:
int b;
private:
int c;
};
公共继承
class B : public A {
public:
int a;
protected:
int b;
//不可访问
int c;
};
保护继承
class B : protected A {
protected:
int a;
int b;
//不可访问
int c;
};
私有继承
class B : private A {
private:
int a;
int b;
//不可访问
int c;
};
父类中的所有非静态成员属性都会被子类继承,父类中私有成员属性,是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了。
父构造-子构造-子析构-父析构
访问子类同名成员,直接访问即可;
访问父类同名成员,需要加作用域
s.Parent::m_A; s.Parent::func();
如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数,如果想访问到父类中被隐藏的同名成员函数,需要加作用域。
class 子类: 继承方式 父类1,继承方式 父类2 ...
class C:public A ,public C{}
两个派生类继承同一个基类,又有某个类同时继承着两个派生类,这种继承被称为菱形继承,或者钻石继承。
函数重载和运算符重载属于静态多态,复用函数名。
派生类和虚函数实现运行时多态。
父类函数必须加virtual关键字。
class Animal {
public:
//virtual 加上该关键字之后变成虚函数
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class Cat : public Animal {
public:
void speak() {
cout << "小猫在说话" << endl;
}
};
class Dog : public Animal {
public:
void speak() {
cout << "小狗在说话" << endl;
}
};
void doSpeak(Animal& animal) {
animal.speak();
}
void test01() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。当类中有了纯虚函数,这个类也称为抽象类,
纯虚函数语法
virtual 返回值类型 函数名 (参数列表) = 0;
抽象类特点
虚析构和纯虚析构共性
虚析构和纯虚析构区别