了解了类,类对象就不难理解。
举例:在现实世界中我们自己就是人类的对象。
换到计算机的角度:对象就是类在实际的内存空间中定义的变量。
现实中任何事物都可以称之为对象,有自己的独特的特点。
面向对象的思想: 就是把一切事物都看成对象,而对象一般都是由属性和方法组成。
属性属于对象静态的一面,用来形容对象的一些特征。 例如:张三的身高、体重、性别。身高、体重和性别就是对象的属性。
方法属于对象动态的一面。 例如:张三会跑,会说话。跑,说话这些行为就是对象的方法。
具有同种属性与行为的对象可以抽象为一个类。
例如:“人”就是一个类,其中的人名叫小明,小红等不同的人都是类在现实世界中“人”的对象。
类相当于一个模板或蓝图,他定义了它所包含的全体对象的共同的属性与行为,对象是类的实例化。
例如:我们在叫小明的时候,不会喊“人”而是说的是“小明”。
总结:类即为一些具有共有属性与行为的抽象。对象就是类的实例。
使用class
来表示一个类
class + 类名
{
private://私有的
public://公有的
protected://受保护
//1.属性
//2.行为
};
类的封装性 = 属性+ 行为 + 访问权限
访问权限:用来修饰类中属性或函数的访问级别的。
public
:公有的,类中或类外对象均可直接访问。
private
:私有的,只有类中可以访问,类外或子类之中均不可以访问。
protected
:受保护的,只有类中或子类的类中可以访问,类外是不可以访问的。
一般情况下:我们会把类中的属性设为私有的,类中方法设定为公有的。
例如封装一个人的类代码示例:
#include
using namespace std;
class person
{
private:
string name;
int age;
public:
void work()
{
//cout << name << endl;//这就叫类中可以直接访问。
cout << "正在学习C++" <<endl;
}
};
int main()
{
person p;
//栈上定义对象
p.work();
person *p1=new person;
//堆上定义对象
p1->work();
//(new Person)->work();不要使用这种方式,这种没有办法释放资源。
delete p1;
return 0;
}
结果展示:
类是由结构体演化而来的。
class
在C++中表示这个类是默认的私有权限,而struct
表示的类默认的是公有权限。
既然类和结构体区别不大,什么时候使用结构体,什么时候使用类?
作为数据节点时,如链表的节点、树的节点时,一般多用struct
。
一般封装逻辑性较多时,都是用class
。
在类外我们不能访问类中的私有属性,所以我们可以在类中手动提供公有的set()
与get()
方法。
代码示例:
在代码中我使用了this
指针,如有疑问,请先看第二个知识点进行了解。
#include
using namespace std;
class person
{
private:
string name;
int age;
public:
void work()
{
cout << "正在学习C++" <<endl;
}
void show()
{
cout << "姓名:" << name << " 年龄:" << age <<endl;
}
//姓名属性的set与get方式
void setname(string name)
{
this->name=name;
}
string getname()
{
return this->name;
}
//年龄属性的set与get方式
void setage(int age)
{
this->age=age;
}
int getage()
{
return this->age;
}
};
int main()
{
person p;
//栈上定义对象
p.setname("yemaoxu");
p.setage(18);
p.show();
p.work();
//cout << p.age <
cout << p.getage() <<endl;
cout << "-----------------------" <<endl;
person *p1=new person;
//堆上定义对象
p1->setname("xiaoming");
p1->setage(20);
p1->show();
p1->work();
cout << p1->getname() <<endl;
//(new Person)->work();不要使用这种方式,这种没有办法释放资源。
delete p1;
return 0;
}
结果展示:
代码展示:
#include
using namespace std;
class A
{
private:
int a;
double b;
public:
void show()
{
cout << "正在学习C++" << endl;
}
};
class B
{
private:
//int a;
//double b;
public:
void show()
{
cout << "正在学习C++" << endl;
}
};
class C
{
private:
//int a;
//double b;
public:
/*void show()
{
cout << "正在学习C++" << endl;
}*/
};
class D
{
private:
static int a;
static double b;
public:
void show()
{
cout << "正在学习C++" << endl;
}
};
int main()
{
A a;
B b;
C c;
D d;
cout << sizeof(a) << endl;
cout << sizeof(b) << endl;
cout << sizeof(c) << endl;
cout << sizeof(d) << endl;
return 0;
}
结果展示:
总结:
unsigned char
的类型的数据。这样的话,即使是一个空类,那么在内存的空间中也有一个表示。class
定义的类,也遵从C中结构体的内存对齐原则。封装一个矩形类,定义矩形的属性(宽,高)及行为(求面积)。
代码示例:
#include
using namespace std;
class Rect
{
private:
//抽象出矩形的属性:
int width;
int height;
public:
int area()//求面积的行为
{
return width*height;
}
//通过外部参数,为类中的属性进行赋值。
void set(int width,int height)
{
this->width=width;
this->height=height;
}
};
//定义一个函数,来比较两个矩形大小,返回最大的那个矩形。
Rect& compare(Rect& r1,Rect& r2)
{
return r1.area()>r2.area() ? r1 : r2;
//在C中函数的返回值默认都是一个右值。
//在C++中如果返回值为一个引用的话,那么此函数的返回值将是一个左值。
}
int main()
{
Rect r1,r2;
r1.set(10,20);
r2.set(30,40);
cout << r1.area() << endl;
cout << r2.area() << endl;
cout << "-------------" << endl;
//获取比较后大的那个矩形,并打印出此矩形的面积:
cout << compare(r1,r2).area() << endl;
return 0;
}
结果展示:
this指针是类中成员函数的一个隐藏形参,哪个类对象调用成员函数,this指针就指向谁。
this的类型:类名 * const this;
C中实现this指针:
#include
#include
typedef struct
{
char *name;
int age;
void (*myfun)();
}stu;
//C++中this的原型就是这个,this就是一个指向本对象的的常指针。
//C++中的成员函数都是带有this的全局函函数。只有生成对象时才可以调用。
void show(stu *const this)
{
printf("%s,%d\n",this->name,this->age);
}
int main()
{
stu s={"yemaoxu",18,show};//this指针的产生,随着对象产生而产生。
//在C++中this指针隐藏在类成员函数中形参列表中的第一位(最左侧)
s.myfun(&s);
return 0;
}
结果展示:
总结:
成员函数参数的入栈过程中,this指针是最后一个,并且直接放在了exc寄存器上。
this就是指向本对象的指针,隐藏在成员函数的最左侧,即第一位。
this指针就是编译器提供给我们程序员使用的,他的用法有以下两种
当函数的形参变量名与类中属性变量相同冲突时,一定要使用this加以区别,也可以通过构造函数的初始化表解决。
初始化列表在第四个知识点进行讲解,感兴趣可以了解一下。
代码示例:
在代码中用到了,构造知识点,感兴趣可以去第三个知识点进行了解。
#include
using namespace std;
class Stu
{
private:
string name;
int age;
public:
Stu(string name,int age)//构造知识
{
this->name=name;
this->age=age;
}
void show()
{
cout << "姓名:" << name << " 年龄:" << age <<endl;
}
};
int main()
{
Stu stu("yemaoxu",18);
stu.show();
return 0;
}
结果展示:
返回本对象。一般情况是当本对象类中的属性被修改时会这么用。
代码示例:
#include
using namespace std;
class Stu
{
private:
string name;
int age;
public:
Stu(string name,int age)//构造知识
{
this->name=name;
this->age=age;
}
Stu& setname(string name)
{
this->name=name;
return *this;
}
void show()
{
cout << "姓名:" << name << " 年龄:" << age <<endl;
}
};
int main()
{
Stu stu("yemaoxu",18);
stu.show();
cout << "-----------" << endl;
stu.setname("xiaoming").show();
return 0;
}
结果展示:
上面两个例子中为什么成员函数show
可以直接访问类中的属性呢?
因为在C++的任何一个非静态成员中都隐藏了一根指向本对象的指针,这个指针就是this,由于this指针的存在,所以类中的非静态成员函数才可以访问类中的成员属性。
注意:
不能在成员函数的形参中使用this指针;
不能在构造函数的初始化表中使用this指针;
可以在成员函数的函数体中使用this指针;
C++中定义构造函数的语法形式:
类名(形参列表)
{
//构造函数的函数体。
//这个函数体就应该是对类中属性进行初始化的逻辑。
}
构造的的意义就是用来初始化类中的属性的。
构造函数的调用时机:
栈区:
类名 对象名(构造函数的实参表);//调用构造函数
堆区:
类名 *指针名;
指针名 = new 类名(构造函数的实参表); //调用构造函数
使用new去定义对象,首先开辟空间,然后调用类中构造函数。
当去定义对象时,编译器就会根据对象后括号的实参类型,自动去调用与之相匹配的构造函数。如果在类中编译器找不到与之匹配的构造函数,将直接报错。
代码示例:
#include
using namespace std;
class Stu
{
private:
string name;
int age;
public:
Stu()
{
cout << "Stu的无参构造" << endl;
}
//构造函数也是函数,他也遵从函数重载原则。
Stu(string name,int age)
{
this->name=name;
this->age=age;
cout << "Stu的有参构造" << endl;
}
void show()
{
cout << "姓名:" << name << " 年龄:" << age <<endl;
}
};
int main()
{
//在栈上定义对象:
Stu stu;//编译器会自动调用无参的空构造。
stu.show();
Stu stu1("yemaoxu",18);//编译器自动调与之参数类型相匹配的有参构造函数。
stu1.show();
cout << "-----------" << endl;
//在堆上定义对象
Stu *pstu=new Stu;
pstu->show();
Stu *pstu1=new Stu("xiaoming",20);
pstu1->show();
delete pstu;
delete pstu1;
return 0;
}
结果展示:
总结:
显式调用与隐式调用
像我们上面举例的方法调用方法就是显式调用。
隐式调用代码示例:
#include
using namespace std;
class A
{
private:
int a;
int b;
int c;
public:
//C++11提供的关键字explicit用于修饰构造函数,以限制编译器自动进行隐式调用。
explicit A(int a)
{
this->a=a;
//cout << "隐式调用了A的有参构造" << endl;
cout << "显式调用了A的有参构造" << endl;
}
A(int a,int b,int c)
{
this->a=a;
this->b=b;
this->c=c;
cout << "另一种方式隐式调用了A的有参构造" << endl;
}
};
int main()
{
A a(10);//显式调用
//A a1=10;//隐式调用
A a2={1,2,3};//隐式调用
return 0;
}
结果显示:
注: 当有explicit
在构造函数前进行修饰时,编译器就不再使隐式调用的方式来调用构造函数了。这样可以提高代码可读性。
构造函数的自动调用的时机总结:
~类名(void){}
析构函数的语法形式:
~类名()
{
//析构函数的函数体。
//函数体就应该书写:当类中有属性指针指向堆区资源的情况,回收资源的逻辑。
//因为在销毁对象时,首先编译器会调用类中的析构函数。
//调用析构函数的意义就是希望你这个程序员来把有以上情况出现时进行回收资源。
}
析构函数的调用时机:
当对象被销毁时,编译器自动调用类中的析构函数。
被销毁分两种:
一种就是栈对象,出栈时自动被销毁,此时也将自动调用类中的析构函数。(能不能完成资源清理,那就看你程序员有没有写回收的逻辑)。
另一种就是堆上的对象:手动delete
销毁,此时也调用类中的析构函数。(能不能完成资源清理,那就看你程序员有没有写回收的逻辑)。
因此,也有人说析构函数也叫清理函数。
析构函数,它与构造函数还不太一样。既可以是编译器自动调用,也可以你这个程序员也可调用。
代码示例:
#include
using namespace std;
class Stu
{
private:
string name;
int age;
int *p;
public:
//Stu有参的构造
Stu(string name,int age)
{
this->name=name;
this->age=age;
this->p=new int[20];
//如果类中有属性指针指向堆区,那么当对象被销毁时,就必须把这个指针指向堆区的资源先回收。
//不然的话就内存泄漏了。
cout << "Stu的有参构造" << endl;
}
~Stu()
{
cout << "Stu的析构" << endl;
delete []p;
//就应该清理类中有属性指针指向堆区的这种情况,在析构函数中书写回收类中属性指针指向的堆区资源的逻辑。
}
};
int main()
{
//在栈上定义对象:
Stu stu("yemaoxu",18);
//在堆上定义对象
Stu *pstu=new Stu("xiaoming",20);
//析构函数不仅可以编译器自动调用,也可以由你程序员在适当时机进行调用。
//delete关键字的底层实现其实就是以下的两步完成了。先析构(清理对象的空间),再free(完成对象的销毁)。
pstu->~Stu();
delete pstu;
return 0;
}
结果展示:
总结:
delete
关键字的底层实现其实就是以下的两步完成了。先析构(清理对象的空间),再free
(完成对象的销毁)。代码示例:
#include
using namespace std;
class Student{
private:
string name;
int *age;
public:
Student(string _name, int _age):name(_name),age(new int(_age)){
cout << this <<" "<< this->name << " 构造函数" << endl;
}
~Student(void){
cout <<this <<" "<< this->name << " 析构函数" << endl;
delete age;
}
};
int main()
{
Student s1("小明", 18);
Student s2("小红", 16);
Student s3("张三", 17);
return 0;
}
结果展示:
类的属性除了一些常用的普通变量(对象)之外,还有一些被修饰符修饰的变量(对象);
两种:
1.const修饰的成员对象、类类型对象(没有默认构造函数的对象)
2.static修饰的成员对象。
在C++中const修饰的变量,必须初始化。
所以const修饰的变量,当开辟空间的同时就必须进行初始。
类中有子类的类类型(但这个类对象没有默认构造函数供其生成对象),也必须在开辟空间的同时,指定一个构造进行初始化。
如何解决这些问题呢?
构造函数的特殊语法:初始化列表
代码示例:
#include
using namespace std;
class A
{
public:
A(int a)
{
cout << "A的有参构造" << endl;
cout << a << endl;
}
~A()
{
cout << "A的析构" << endl;
}
};
class stu
{
private:
string name;
int age;
const int id;
int& b;
A a;
public:
stu(string _name,int _age,int _id,int& _b):name(_name),age(_age),id(_id),b(_b),a(1)
{
cout << "stu中的有参构造" << endl;
}
void show()
{
cout << "姓名:" << name <<" 年龄:" << age << " 学号:" << id << " 引用数" << b << endl;
}
~stu()
{
cout << "stu的析构" << endl;
}
};
int main()
{
int num=521;
stu s("yemaoxu",18,1001,num);
s.show();
return 0;
}
结果展示:
总结:
a(1)
就是用来告诉编译器指定调用A类中的那一个构造来完成类中a对象初始化的。当一个进程被加载时,系统会为这个进程分配一块4G的虚拟地址空间。
类中的静态属性,是所有对象所共享的一份属性。
是因为静态数据只能被加载一次。静态区的部分是属于整个进程的。
它只有一份数据。
因为静态数据,在程序加载时就已经被确定下来了。
类中的静态属性,在语法层面上,他是属性整个类,而不是某个对象。他为整个类服务,而非某一个对象。如果是public
修饰的话,那么也可以直接使用域名访问符::
的形式直接访问,而无需依赖某个对象调用。 所以类中的静态属性是不依赖于对象,他是属于整个类的。
当类中如果有需要定义一个为整个类而服务属性时,就可以把它升级静态属性。
这样的话这个属性就不再依赖于某个对象。
由于静态成员变量定义在静态区定义内存,而对象是存在于动态区之中,所以静态成员变量并不占用类对象的内存空间。 这个我在前面的代码中sizeof
打印演示过。
静态区变量只能被初始化一次。
代码示例:
#include
using namespace std;
class stu
{
private:
string name;
int age;
const int id;
public:
static int count;
stu(string _name,int _age,int _id):name(_name),age(_age),id(_id)
{
cout << "stu中的有参构造" << endl;
count++;
}
void show()
{
cout << "姓名:" << name <<" 年龄:" << age << " 学号:" << id << endl;
cout << "学生人数:" << count << endl;
}
~stu()
{
cout << "stu的析构" << endl;
}
};
int stu::count;
int main()
{
stu s("yemaoxu",18,1001);
cout << "学生人数:" << s.count << endl;
stu s1("xiaoming",20,1002);
cout << "学生人数:" << s1.count << endl;
s.show();
cout << "-----------------" << endl;
cout << &s.count << endl;
cout << &s1.count << endl;
return 0;
}
结果展示:
总结:
static
修饰的成员变量时int Stu::count;