C++快速讲解(五):类和对象
前言:主要介绍了对象的创建、访问修饰符、实现类的成员函数、构造函数、析构函数、拷贝构造函数、移动构造函数、常函数和常对象、静态变量和函数、友元等。
1.类和对象
类是构成对象的一个蓝图、可以拥于属性(用于表示数据)、可以拥有方法 ( 用于表示行为动作 )、可以隐藏数据和方法、可以对外提供公开的接口
1.1 在栈中创建对象
#include <iostream>
#include <string>
using namespace std;
//创建一个类
class stu{
private://私用的属性
string name;
int age;
public://公共的方法
void read(){
cout<<"look book"<<endl;
}
};
int main() {
stu s1; //在栈中创建对象
s1.read();//访问read函数
stu s2;//在栈中创建对象
s2.read();
return 0;
}
1.2 在堆中创建对象
#include <iostream>
#include <string>
using namespace std;
//创建一个类
class stu{
private://私用的属性
string name;
int age;
public://公共的方法
void read(){
cout<<"look book"<<endl;
}
};
int main() {
stu *s3 = new stu;//在堆中创建对象
s3->read();//访问对象的方法
// *s3->read();//另一种访问方法
delete s3;//进行回收内存
return 0;
}
2.访问修饰符
- public : 公开权限,任何位置都可以访问
- private : 私有权限,只能自己内部访问及其友元函数
- protected : 类内、子类及友元函数可访问
#include <iostream>
#include <string>
using namespace std;
//创建一个类
class stu{
private://私用的属性
string name;
public://公共的方法
int age;
void run(){
cout<<"run:----"<<age<<endl;
}
};
int main() {
stu s1; //在栈中创建对象
// s1.name = "fly";//不能访问,因为是private
s1.age = 18;
s1.run();//可以访问
return 0;
}
3.实现类的成员函数
3.1 实现
#include <iostream>
#include <string>
using namespace std;
//创建一个类
class stu{
private://私用的属性
string name;
int age;
public://公共的方法
void read(string bookname){
cout<<"bookname:----"<<bookname<<endl;
}
void speak(string str);
};
//在类的外部实现函数
void stu::speak(string str) {
cout<<"out-speak--"<<str<<endl;
}
int main() {
stu s1; //在栈中创建对象
s1.read("san ti");
s1.speak("i love move");
return 0;
}
3.2 分离实现
stu.h
//
// Created by Fly on 2021/3/21.
//
#ifndef DAY04_STU_H
#define DAY04_STU_H
#include <string>
using namespace std;
class Student{
private:
int age;
string name;
public:
void read(string name);
void speak(string something);
};
#endif //DAY04_STU_H
stu.cpp
//
// Created by Fly on 2021/3/21.
//
#include "stu.h"
#include <iostream>
using namespace std;
void Student::read(string name) {
cout<<"name"<<name<<endl;
}
void Student::speak(string something) {
cout<<"something"<<something<<endl;
}
main代码
#include <iostream>
#include "stu.h"
int main() {
Student s;
s.read("三体");
s.speak("你好!");
return 0;
}
4.构造函数
构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。与类名同名,没有返回值,可以被重载,通常用来做初始化工作。
4.1 一般方式构造
#include <iostream>
#include <string>
using namespace std;
//一般方式构造
class stu{
string name;
int age;
public:
//构造函数
stu(){
cout<<"无参数构造函数"<<endl;
}
//有参构造
stu(string val_name){
cout<<"一个参数构造函数"<<endl;
name = val_name;//赋值给类的name
}
//有两个参数的构造函数
stu(string val_name,int val_age){
cout<<"两个参数构造函数"<<endl;
name = val_name;//赋值给类的name
age = val_age;//赋值给类的age
}
void run(){
cout<<"name"<<name<<endl;
cout<<"age"<<age<<endl;
}
};
int main() {
stu s1;
s1.run();
stu s2("fly");
s2.run();
stu s3 ("张三",28);
s3.run();
return 0;
}
4.2 初始化列表方式
#include <iostream>
#include <string>
using namespace std;
class stu{
string name;
int age;
public:
//构造函数
stu(){
cout<<"无参数构造函数"<<endl;
}
//初始化列表方法
stu(string name,int age):name{name},age{age}{
cout<<"两个参数构造函数"<<endl;
}
void run(){
cout<<"name"<<name<<endl;
cout<<"age"<<age<<endl;
}
};
int main() {
stu s3("张三",28);
s3.run();
return 0;
}
4.3 委托构造函数
#include <iostream>
#include <string>
using namespace std;
class stu{
string name;
int age;
public:
//构造函数
stu():stu("fly",26){
cout<<"无参数构造函数"<<endl;
}
//构造函数
stu(string name):stu(name,26){
cout<<"一个参数构造函数"<<endl;
}
stu(string name,int age):name{name},age{age}{
cout<<"两个参数构造函数"<<endl;
}
};
int main() {
stu s1;
stu s2("fly");
stu s3("fly", 26);
return 0;
}
4.4 this构造
#include <iostream>
#include <string>
using namespace std;
class Student{
public:
int age;
string name;
Student(int age, string name){
this->name = name;
this->age = age;
}
void runName(){
cout<<"age:"<<age<<endl;
}
void runAge(){
cout<<"name:"<<name<<endl;
}
};
int main(){
Student s1(18,"fly");
s1.runAge();
s1.runName();
return 0;
}
5.析构函数
类的析构函数是类的一种特殊的成员函数,与构造函数正好相反,它会在每次删除所创建的对象时执行。一般用于释放资源。
#include <iostream>
#include <string>
using namespace std;
//析构函数
//类的析构函数是类的一种特殊的成员函数,与构造函数正好相反,它会在每次删除所创建的对象时执行。
class stu{
string name;
int age;
public:
//构造函数
stu(){
cout<<"无参数构造函数"<<endl;
}
//构造函数
stu(string name){
cout<<"一个参数构造函数"<<endl;
}
stu(string name,int age){
cout<<"两个参数构造函数"<<endl;
}
//析构函数
~stu(){
cout<<"执行析构函数"<<endl;
}
};
int main() {
stu *s1 = new stu;
stu *s2 = new stu("fly");
stu *s3 = new stu("fly",26);
delete s1;
delete s2;
delete s3;
return 0;
}
6.拷贝构造函数
6.1 引出浅拷贝
对象的复制
#include <iostream>
#include <string>
using namespace std;
//认识拷贝
class stu{
public:
string name;
int age;
//有两个参数的构造函数
stu(string name,int age): name{name},age{age}{
cout<<"两个参数构造函数"<<endl;
}
~stu(){
cout<<"执行析构函数"<<endl;
}
};
int main() {
stu s1("fly",26);
cout << s1.name << " : " << s1.age <<endl;
cout <<"s1.name的地址"<< &s1.name<<endl;
stu s2 = s1;
cout << s2.name << " : " << s2.age <<endl;
cout <<"s2.name的地址"<< &s2.name<<endl;
return 0;
}
从运行结果我们可以看出对象的复制不是简单的地址的复制,是复制了另一个的数据,然后重新建立一个储存空间(地址不一样可以得出),这个就是类里面拷贝构造函数的实现。
6.2 浅拷贝
#include <iostream>
#include <string>
using namespace std;
//浅拷贝
//指的是在对象复制时,只对对象中的数据成员进行简单的赋值,
// 默认拷贝构造函数执行的也是浅拷贝。
// 如果数据中有属于动态成员 ( 在堆内存存放 ) ,那么浅拷贝只是做指向而已,不会开辟新的空间。默认情况下,编译器提供的拷贝操作即是浅拷贝。
class stu{
public:
string name;
int age;
//有两个参数的构造函数
stu(string name,int age): name{name},age{age}{
cout<<"两个参数构造函数"<<endl;
}
//拷贝构造函数
stu(const stu &s){
cout<<"拷贝函数"<<endl;
name = s.name;
age = s.age;
}
~stu(){
cout<<"执行析构函数"<<endl;
}
};
int main() {
stu s1("fly",26);
cout << s1.name << " : " << s1.age <<endl;
cout <<"s1.name的地址"<< &s1.name<<endl;
stu s2 = s1;
cout << s2.name << " : " << s2.age <<endl;
cout <<"s2.name的地址"<< &s2.name<<endl;
return 0;
}
6.3 浅拷贝带来的问题
默认情况下,浅拷贝已经足以应付日常的需求了,但是当类中的成员存在动态成员(指针)时,浅拷贝往往会出现一些奇怪的问题。
#include <iostream>
#include <string>
using namespace std;
//浅拷贝引发的问题
class stu{
public:
string name;
string *address = nullptr;
//有两个参数的构造函数
stu(string name,string *address): name{name},address{address}{
cout<<"两个参数构造函数"<<endl;
}
//拷贝构造函数
stu(const stu &s){
cout<<"拷贝函数"<<endl;
name = s.name;
address = s.address;
}
~stu(){
cout<<"执行析构函数"<<endl;
//这里将会删除两次内存空间
delete address;
address = nullptr;
}
};
int main() {
string address = "shenzhen";
stu s1("fly",&address);
cout << "s1.name:"<<s1.name <<" &s1.name:" <<&s1.name<< " *s1.address:" << *s1.address <<" s1.address:"<<s1.address <<endl;
//执行拷贝
stu s2 = s1;
cout << "s2.name:"<<s2.name <<" &s2.name:" <<&s2.name<< " *s2.address:" << *s2.address <<" s2.address:"<<s2.address <<endl;
//修改第一个学生的地址,第二个也会跟着变化
*s1.address = "beijing";
cout << "s1.name:"<<s1.name <<" &s1.name:" <<&s1.name<< " *s1.address:" << *s1.address <<" s1.address:"<<s1.address <<endl;
cout << "s2.name:"<<s2.name <<" &s2.name:" <<&s2.name<< " *s2.address:" << *s2.address <<" s2.address:"<<s2.address <<endl;
return 0;
}
从这里我们可以看出,执行系统提供的浅拷贝的时候,当类中的成员存在动态成员(指针)时,浅拷贝就会失效,这里修改第一个学生的地址,第二个也会跟着变化,所以可以得出结论,浅拷贝的时候,只是把地址拷贝了一份,两个地址指向同一个储存空间,所以我们需要深拷贝来解决这个问题。
6.4 深拷贝
为了解决当类中的成员存在动态成员(指针)时,浅拷贝就会失效的问题,我们需要自己实现 深拷贝,来解决这一问题。
#include <iostream>
#include <string>
using namespace std;
//深拷贝
class stu{
public:
string name;
string *address = nullptr;
//有两个参数的构造函数
stu(string name,string *address): name{name},address{address}{
cout<<"两个参数构造函数"<<endl;
}
//拷贝构造函数
stu(const stu &s){
cout<<"拷贝函数"<<endl;
name = s.name;
//实现深拷贝
if (address == nullptr){
address = new string(*s.address) ;
}
}
~stu(){
cout<<"执行析构函数"<<endl;
//这里将会删除两次内存空间
delete address;
address = nullptr;
}
};
int main() {
string address = "shenzhen";
stu s1("fly",&address);
cout << "s1.name:"<<s1.name <<" &s1.name:" <<&s1.name<< " *s1.address:" << *s1.address <<" s1.address:"<<s1.address <<endl;
//执行拷贝
stu s2 = s1;
cout << "s2.name:"<<s2.name <<" &s2.name:" <<&s2.name<< " *s2.address:" << *s2.address <<" s2.address:"<<s2.address <<endl;
//修改第一个学生的地址,这时候第二个不会变化
*s1.address = "beijing";
cout << "s1.name:"<<s1.name <<" &s1.name:" <<&s1.name<< " *s1.address:" << *s1.address <<" s1.address:"<<s1.address <<endl;
cout << "s2.name:"<<s2.name <<" &s2.name:" <<&s2.name<< " *s2.address:" << *s2.address <<" s2.address:"<<s2.address <<endl;
return 0;
}
6.5 触发拷贝的场景
#include <iostream>
#include <string>
using namespace std;
//触发拷贝的场景
class stu{
public:
string name;
string address;
//有两个参数的构造函数
stu(string name,string address): name{name},address{address}{
cout<<"两个参数构造函数"<<endl;
}
//拷贝构造函数
stu(const stu &s){
cout<<"拷贝函数"<<endl;
name = s.name;
address = s.address;
}
~stu(){
cout<<"执行析构函数"<<endl;
}
};
void printStu(stu s){
cout << s.name << " : "<< s.address << endl;
}
stu createStu(){
return stu("张三","beijing");
}
int main() {
//对象创建依赖于其他对象
cout << "---对象创建依赖于其他对象---" << endl;
stu s1("张三","shenzhen"); //执行构造函数
stu s2 = s1; //执行拷贝构造函数
cout << "------" << endl;
//函数参数
cout << "---函数参数---" << endl;
stu s3("fly","changsha"); //执行构造函数
printStu(s3); //执行拷贝构造函数
cout << "------" << endl;
//函数返回值
cout << "---函数返回值---" << endl;
stu s4= createStu();
cout << "------" << endl;
return 0;
}
7.移动构造函数
使用移动构造,会使得在返回对象时,不会调用拷贝构造函数,这也就避免产生了对象的拷贝工作。
#include <iostream>
#include <string>
using namespace std;
//使用移动构造
class Student{
public :
int *age;
Student():age(new int(18)){
cout << "执行构造函数~!~"<<endl;
}
//深拷贝
Student(const Student &s):age(new int(*s.age)){
cout << "执行拷贝构造函数~!~"<<endl;
}
//移动构造
Student( Student &&s):age(s.age){
cout << "执行移动!!!构造函数~!~"<<endl;
//移动之后,一般即可让原有对象的指针变成空指针
s.age = nullptr;
}
//析构
~Student(){
cout <<"执行析构函数~!" << endl;
delete age;
}
};
Student getStu(){
Student s ;
cout <<"getTemp =" << __func__ << " : " << hex << s.age << endl;
return s;
}
int main() {
Student stu = getStu();
cout <<"stu.age:" << stu.age << endl;
Student stu3 = move(stu);
cout <<"stu3.age:" << stu3.age << endl;
cout <<"stu.age:" << stu.age << endl;
return 0 ;
}
8.this 指针
this只能在成员函数中使用。全局函数,静态函数都不能使用this。全局函数不属于任何一个类,静态函数也不依赖任何一个类,所以他们都不会有this指针 。 在外部看来,每一个对象都拥有自己的成员函数,一般情况下不写this,而是让系统进行默认配置。this指针永远指向当前对象 , 所以成员函数中的,通过this即可知道操作的是哪个对象的数据。
#include <iostream>
#include <string>
using namespace std;
class Student{
public:
int age;
string name;
Student(int age, string name){
this->name = name;
this->age = age;
}
Student &getStu(){
return *this;
}
void runName(){
cout<<"age:"<<age<<endl;
}
void runAge(){
cout<<"name:"<<name<<endl;
}
};
int main(){
Student s1(18,"fly");
Student &s2 = s1.getStu(); //使用引用来接收,表示指向同一个引用,
s2.runAge();
s2.runName();
return 0;
}
9.常函数和常对象
- 如果确定了某个成员函数不会修改成员变量,那么可以把该函数声明为常函数。函数体内不允许修改成员变量,除非该变量使用mutable修饰。
- 常对象其实也就是一个变量,和以前的常量没有相差多少。不同的是如今对象里面可以能包含很多的变量,以前的常量指的只是一个变量而已。 若类中有变量使用mutable修饰,那么该变量在常对象状态下,可以被修改。
#include <iostream>
#include <string>
using namespace std;
class Student{
public:
string name = "无名氏";
int age = 18; //除非该变量使用mutable修饰.
Student(string name,int age){
this->name = name;
this->age = age;
}
//在此添加 const 即可变成常函数,
void printStudent() const{
// age = 10 ; 错误,不允许修改
cout << name << " " << age << endl;
}
};
int main(){
const Student s1 ("zhangsan " ,18);
cout << s1.name << endl; //访问常对象中的成员 ,OK
// s1.name = "李四" ; // 试图修改常对象中的成员 , error
return 0;
}
10.静态变量和函数
#include <iostream>
#include <string>
using namespace std;
class Student{
public:
int age ;
string name;
static string school; //静态成员
static string getSchool(){//静态函数
//不能访问 name 和 age
return school;
}
};
//在类的外面对类的静态成员进行初始化。
string Student::school = "beijing un";
int main(){
Student s ;
cout << s.getSchool() << endl; //可以访问
//也可以使用类名的方式访问
cout << Student::school <<endl;
return 0 ;
}
注意:
- 静态成员属于类,不属于对象
- 静态成员变量必须在类中声明,在类的外部初始化
- 静态成员变量的声明周期是整个程序,作用域在类的内部
- 静态成员函数只能访问静态成员变量
- 静态成员可以使用类来访问,也可以使用对象来访问。
11.友元
11.1 友元函数
#include <iostream>
#include <string>
using namespace std;
//友元函数
class Student{
private:
string name = "fly";
friend void showName(Student s);
};
void showName(Student s){
cout<<s.name<<endl;
}
int main(){
Student s;
showName(s);
return 0 ;
}
11.2 友元类
#include <iostream>
#include <string>
using namespace std;
//友元类
class Student{
private:
string name = "fly";
friend class S2;
public:
string getName(){
return name;
}
};
class S2{
public:
void changeName(Student &s){
s.name = "wang";
}
};
int main(){
Student s;
S2 s2;
s2.changeName(s);
cout<<s.getName()<<endl;
return 0 ;
}
结束。