类和对象
认识类和对象
先来回忆一下C语言中的类型和变量,类型就像是定义了数据的规则,而变量则是根据这些规则来实际存储数据的容器。类是我们自己定义的一种数据类型,而对象则是这种数据类型的一个具体实例。类就可以理解为类型,而对象就可以理解为变量,而且对象是更加高级的变量。
类型 = 类型数据 + 类型操作
比如double类型
他的数据类型占用8字节,并且可以表示成小数。
类型操作可以加减乘除,但是它不支持模(%)运算。
那么类就是包含了类型数据和类型操作的一个集合。
也可以理解为数据就是类的属性,操作就是类的行为
通过下面我定义的一个人的类,那么height,sex, age都是这个类的类型数据,而类型操作就是函数run和函数speak。
class People {
double height;//身高
char sex;//性别
int age;//年龄
string name;//姓名
void run() {//跑
cout << "I can run" << endl;
}
void speak() {//说话
cout << "I can speak" << endl;
}
};
那么我需要一个特定的人的时候,我就需要定义一个对象用来存这个特定人的属性。
下面的用类定义一个对象,并且对他的属性进行赋值。
People a;
a.name = "Zhang san";
a.age = 18;
a.sex = '1';//女0,男1
a.height = 1.89;
而我需要这个人进行说话或者跑时,就需要调用他相应的操作,也就是函数。
a.run();
a.speak();
访问权限
public:是公共的资源,是谁都可以访问的资源,比如空气,阳光,公共场合都是谁都可以访问进行使用的。
private:是私有的资源,是只能类自己进行访问的资源,比如每个人自己的私人物品,是只能自己使用和访问的。
protected:是受保护的资源,是只能自己访问以及自己的子类访问的资源,比如你儿子可以使用你的东西。(在后续文章继承时会用到protected权限)
friendly:是在关键字friend声明后,它允许被声明后的类或函数访问这一个类的私有或受保护的资源,比如你的好兄弟在你的允许下可以访问和使用你的私人物品。(在后续的文章中会更新到使用)
重点:这里的访问权限是规定的类外对该类类内的访问权限。
比如我在一个类中定义了一个访问权限为私有的方法,那么我只能在这个类的内部进行调用,在类外是无法调用的,除非我对这个方向进行了friend友元声明。
对于为什么要为什么要有访问权限的设置,看如下代码:
#include<iostream>
using namespace std;
class People {
public :
int age;
void speak() {
cout << "my age is "<< age << endl;
return ;
}
};
int main() {
People a;
a.age = -100;
a.speak();
return 0;
}
如果对于age是public公有访问的,那么在设置年龄的将不会受到限制,对于这份代码可能没有问题,如果在以后设计项目或者工程时,就会造成灾难性的错误。
那么正确的写法应该如下,把age设置为私有的,对age赋值时,定义一个函数来进行对age赋值,并判断值是否在正确范围内,对于获取age值也定义一个函数进行来获取。
#include<iostream>
using namespace std;
class People {
public :
void speak() {
cout << "my age is "<< age << endl;
return ;
}
void set_age(int x) {
if (age <= 0 || age > 1000) {
perror("your age is error\n");
exit(1);
}
age = x;
return ;
}
int get_age() {
return age;
}
private :
int age;
};
int main() {
People a;
a.set_age(-100);
a.speak();
return 0;
}
构造函数和析构函数
定义一个对象的流程:
对于构造函数的详细解析如下代码:
#include<iostream>
using namespace std;
class A {
public :
A() {//默认构造函数
cout << "default constructor" << endl;
}
//如果这里没有const关键字,传入的对象是const声明的那么就会报错
//如果没有&(引用),那么将会造成无限套娃的拷贝构造
//假如这里没有&,那么这里的形参就相当于实参给他赋值
//而赋值就是 A a = b, b是实参传入的值
//那这里就会又调用构造函数,形成无限套娃
A(const A &a) {
this->x = a.x;
this->y = a.y;
cout << "copy constructor" << endl;
}
A(int x, int y) {//有参构造函数
cout << "have parameter constructor" << endl;
this->x = x;//this表示当前对象的地址
this->y = y;
}
void operator=(const A &a) {//这个函数是=运算符重载,是类中默认有的
cout << "this is assigment" << endl;
return ;
}
A(int x) {//转换构造函数
cout << "conversion constructor" << endl;
this->x = x;
this->y = 0;
}
void output() {
cout << "(" << x << ", " << y << ")" << endl;
}
~A() {//析构函数
cout << "destructor" << endl;
}
private :
int x, y;
};
void func(A a) {
cout << "func : ";
a.output();
return ;
}
int main() {
A a;//调用默认构造函数
cout << "1-------------------------" << endl;
A b(1, 2);//调用有参构造
cout << "2-------------------------" << endl;
//这里就调用了拷贝构造函数
A e = b;
cout << "3-------------------------" << endl;
//这里只是运用了普通赋值运算符
//通过执行会发现没有执行任何构造函数
a = b;
cout << "4-------------------------" << endl;
//调用转换构造,将int类型转换为A类型
//这里也可以是另一个类型的对象,那也是转换构造
//不过还需要从新定义一个需要转换构造的函数,里面的参数就是对应的类型
A c(3);
cout << "5-------------------------" << endl;
//这里调用的是拷贝构造函数,将对象b进行拷贝给函数形参a
func(b);
func(c);
cout << "6-------------------------" << endl;
//对于这中函数调用情况
//这里就会用到转换构造
//他会隐式的将int类型5通过转换构造转会为A类型
func(5);
cout << "7-------------------------" << endl;
//这里相当于A d(4);
//这里可以这样理解
//等号类型相同编程才能通过
//那么4,int类型就会通过转换构造变成A类型
//如果没有转换构造那么就编译无法通过
A d = 4;
a = 6;
cout << "8-------------------------" << endl;
return 0;
}
构造函数的初始化列表
构造函数中的初始列表使用:
#include<iostream>
using namespace std;
class C {
public:
C(int n) {
this->num = n;
cout << "C coversion constructor" << endl;
}
void output() {
cout << num;
}
private :
int num;
};
class A {
public :
//:后面是构造函数的初始化列表
A(int a) : a(a), b(10), c(6) {
cout << "A constructor" << endl;
}
void output() {
cout << " " <<a << " " << b << " ";
c.output();
cout << endl;
return ;
}
private :
int a, b;
C c;
};
int main() {
A a(2);
a.output();
return 0;
}
构造函数和析构函数的调用顺序
如下这份代码:
#include <iostream>
#include <string>
using namespace std;
class A {
public :
A() {};
A(string n) {
name = n;
cout << name << " class A construtor" << endl;
}
~A() {
cout << name << " class A destructor" << endl;
}
int *p;
private :
string name;
};
class B {
public :
//:a(a)
//:后面是构造函数时,最该对象中的成员属性进行初始化操作
B(string n, A &a) : a(a){
name = n;
cout << name << " class B construtor" << endl;
//new关键字可以理解为,C语言中malloc去堆区中开辟一段空间
a.p = new int[10];
return ;
}
A &a;//创建一个引用属性,这里&a可以暂时理解为取a的地址
~B() {
//delete就相当于C语言中的free对开辟空间的释放
delete a.p;
cout << name << " class B destructor" << endl;
}
private :
string name;
};
int main() {
A a("a");
B b("b", a);
return 0;
}
执行结果:
可以发现,析构顺序是构造顺序反过来的,通过代码可以发现在B中的有参构造中用到了a对象的引用,然后对a对象的p指针开辟了一段空间,如果析构时顺序和构造顺序一样的那么,在析构b对象时,去delete a.p去释放开辟的空间时,a对象已经被析构掉了,那就也无法去找a对象中的指针p了。