一、C++中的类
1、类的三大特性:
-
封装:
- 1、把功能函数接口封装到类中
- 2、把数据成员封装到类中
-
继承:
- 1、子类继承父类的函数接口或者数据,不需要重新开发接口,提高代码复用性
-
多态:
- 同一操作作用于不同的对象,可以有不同的解释,从而实现不同的结果。它能够提高代码的灵活性和可维护性。
2、面向对象编程的优点
- 1、代码容易修改与维护(因为每个对象是独立的,只需要修改对象的内容即可)
- 2、提高代码复用性(有继承、多态、函数重载),会少些很多代码
- 3、能更好的设计复杂的程序
二、C++中类的定义
class 类名
{
//成员列表
}
这是最简单的定义方式,但是这样定义出来的类,是没有任何意义的。因为类中的成员都是私有成员,外界是无法访问的。
1、一般类的定义方式
class 类名
{
public : //共有成员,外界可以访问
protected : //保护成员,只有派生类可以访问
private : //私有成员,只有当前类内部可以访问
}
定义一个类的例子:
class base
{
public:
int a; //外界可以访问,因为是共有成员
protected:
int b; //子类可以访问,因为是保护成员
private:
int c; //只有类的内部可以访问 ,因为是私有成员
};
三、C++中类的构造函数
1、构造函数:
- 1、函数名与类名相同
- 2、函数没有返回值
- 3、函数不需要调用,在创建对象时会自动调用
2、构造函数的作用:
- 用于在创建对象时,对对象中的数据成员进行初始化。
3、构造函数的语法:
class 类名
{
public:
类名() //构造函数
{
}
}
例子:
class base
{
public:
base()
{
cout << "调用构造函数" << endl;
}
}
注意:
- 构造函数必须要写在公共区,因为在创建对象时会自动调用构造函数。如果不在公共区,则无法调用,会导致创建对象失败
- 假设用户没有写任何的构造函数,则系统会自动生成一个无参的构造函数
提示:
- 1、构造函数也支持函数重载
- 2、构造函数也支持默认参数
练习:设计一个学生类 ,共有成员 name ,保护成员money ,私有成员 id ,设计一个构造函数去初始化这些数据成员。 并设计一个输出接口。
#include <iostream>
using namespace std;
extern "C"
{
#include <string.h>
}
class student
{
public:
student(const char *Name, int Money, int ID) : Money(Money), ID(ID)
{
strcpy(this->Name, Name);
}
void show()
{
cout << this->Name << this->Money << this->ID << endl;
}
char Name[32];
protected:
int Money;
private:
int ID;
};
int main()
{
student stu("张三", 100, 180);
stu.show();
}
四、this 指针
作用:
- 用来区分类内成员与内外成员
- 每当用户创建一个对象时,这个类就会自动生成一个this指针,指向当前类的首地址
base(int a,int b,int c)
{
//this->a 当前类-》a 成员
this->a=a;
this->b=b;
this->c=c;
}
//不使用 this 指针那么 a ,b ,c 变量的名字要重新修改!!
五、malloc 与 new 的区别
#include <iostream>
using namespace std;
extern "C"
{
#include <stdlib.h>
}
class base
{
public:
base(int a)
{
cout << "调用based的构造函数" <<endl;
this->a = a;
}
void show()
{
cout << a << endl;
}
private:
int a;
};
int main()
{
//利用malloc分配base的空间
base *p = (base *)malloc(sizeof(base));
p->show();
//利用new分配base空间
base *q = new base(10086);
q->show();
}
注意:
- 使用malloc开辟类的空间,不会调用类的构造函数,会导致类中的成员无法初始化
- 使用new开辟类的堆空间,可以调用构造函数去初始化类中的成员
六、析构函数
- 1、函数名与类名相同,在函数名前面加 ~
- 2、析构函数没有返回值,没有参数
- 3、当对象销毁时,自动调用。可以用析构函数来释放类中成员的空间
作用:
- 用于释放构造函数中初始化的数据成员
注意:
- 析构函数是不可以重载的,因为重载的依据是函数参数, 析构函数没有参数
- 析构函数也要编写到公共区域中,因为对象销毁时自动调用
七、类中的大小计算
字节对齐原则:
回顾结构体的大小计算:
struct node
{
char a;
int b;
short c;
double d;
} //在64位的操作系统中计算该结构体的大小 -》24
总结:
- 结构体的空间大小是根据字节对齐原则的,在32位与64位系统中对齐原则是不一样的。 具体的对齐原则请看上述表格
类中的空间大小分配与结构体一样
//当前类中 最大的字节是 int 所以按照4字节对齐原则
class base
{
char a; //分配4个 char使用 1个剩下 3 个
short b; //剩下的3个可以提供2个给 short去使用 所以剩下1 个
int c; //重新分配 4 个空间
}; //->8
class base1
{
char a; //分配4个
int c; //分配4个
short b; //分配4个
}; //根据 4 自己对齐原则 -》 12
结论:
- 在设计类的数据成员时,应该把数据类型,从小到大排序的去定义,这样定义出来的类,所占用的内存空间最小。
类中的大小与成员属性关系:
类中的大小与成员函数的关系:
class base2
{
public:
void func() //在没有调用 func 的时候,b,c的空间是不会分配
{
//b 和 c 根本都不属于 base2类的空间中
int b;//func 里面的局部变量 ,局部变量是临时存在的
int c;
}
int a;
}; //4
结论:
- 类中的空间大小,只于类中的数据成员有关,与类中的成员函数无关。(除了虚函数)
提示: 空类的大小为1
八、构造函数的参数列表初始化
作用:
- 在构造函数中的一种特殊初始化方式,用于初始化类中的数据成员
提示:
- 这种初始化方式并不是万能的,如果数据类型不支持 = 赋值,就不可用该方式。列如:数组、字符串
九、拷贝构造函数 (重点,难点)
系统自动生成的浅拷贝方法::
#include <iostream>
using namespace std;
class base
{
public:
base(int a, int b, int c) : a(a), b(b), c(c){}
void show()
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
private:
int a, b, c;
};
int main()
{
base a(10, 20, 30);
a.show();
//通过一个对象去初始化另外一个人对象
base b = a;
//系统会自动生成一个浅拷贝构造函数,把a对象的数据赋值给b对象
b.show();
}
重写系统的拷贝构造函数:
- 在重写拷贝构造函数时,应当传递参数为引用,此时会重写系统默认的拷贝构造函数。通过重写的拷贝构造函数,可以实现深拷贝。
语法:
class 类名
{
public:
类名(类名 &a) //重写后的拷贝构造函数 ,
{
}
}
提示:
- 当用户重写拷贝构造函数之后,系统就不会自动生成一个浅拷贝的方法
#include <iostream>
using namespace std;
class base
{
public:
base(int a, int b, int c) : a(a), b(b), c(c){}
base (base &a)
{
cout << "调用拷贝构造函数" << endl;
this->a = a.a;
this->b = a.b;
this->c = a.c;
}
void show()
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
private:
int a, b, c;
};
int main()
{
base a(10, 20, 30);
a.show();
//通过一个对象去初始化另外一个人对象
base b = a;
//系统会自动生成一个浅拷贝构造函数,把a对象的数据赋值给b对象
b.show();
}
十、深拷贝
为什么需要深拷贝??
- 因为两个对象在共用一个地址空间。这是非常危险的。
重写拷贝构造函数,实现深拷贝:
#include <iostream>
using namespace std;
extern "C"
{
#include <string.h>
}
class base
{
public:
base (const char *str)
{
//分配p所指向的堆空间
p = new char[1024];
//把str数据赋值到堆空间中
strcpy(p, str);
}
//重写拷贝构造函数,实现深拷贝!!!!!
base (base &a)
{
cout << "调用重写后的拷贝构造函数,实现深拷贝"<< endl;
//分配p所指向的堆空间
p = new char[1024];
strcpy(p, a.p);
}
void show()
{
cout << "堆空间的地址" << (void *)p <<"内容" << p << endl;
}
char *p;
};
int main()
{
base a("hello");
a.show();
base b = a;
b.show();
}
使用深拷贝之后,两个对象操作的堆空间地址就不一样了。
十一、类外定义类中的成员函数
语法:
返回值 类名::函数名(参数列表)
{
}
-----------------------------------
例子:
class base
{
public:
void show(); //声明
};
//类外定义show 接口
void base::show()
{
cout << "show base" << endl;
}
练习:内外实现函数成员
#include <iostream>
using namespace std;
extern "C"
{
#include <string.h>
}
class base
{
public:
base(int size,const char *str);
base(base &a); //深拷贝函数
~base();
void show();
private:
int size;
char *p; //指向一块堆空间
};
base :: base(int size,const char *str)
{
cout << "base 构造函数" << endl;
this->size = size;
p = new char[size];
strcpy(p, str);
}
base :: base(base &a)
{
cout << "base 深拷贝构造函数" << endl;
p = new char[a.size];
strcpy(p, a.p);
}
base :: ~base()
{
cout << "base 析构函数" << endl;
delete []p;
}
void base :: show()
{
cout << p << endl;
}
int main()
{
base a(1024, "Hello");
a.show();
base b = a;
b.show();
}