文章目录
- C语言中的结构体将属性和行为分开处理,在调用行为时无法进行匹配
- C++中的Class将属性和行为封装到了一起,属性与行为不匹配无法运行
控制权限: public 公共的 protected 受保护的 private 私有的
在C++中 class 和 struct 一样,但是 struct 默认权限是 public, class 是 private的
构造函数和析构函数
构造函数:在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用
析构函数:对象销毁前系统自动调用,执行清理工作
- 构造函数函数名与类名相同,无返回值,不需要写void,可以重载
- 析构函数函数名与类名相同,无返回值,无参数,前面必须加~,不能重载
- 用户没写构造析构函数,系统会自动调用一个为空的函数。
class Person{
public:
Person(){cout << "构造函数" << endl;}
~Person(){cout << "析构函数" << endl;}
private:
int age;
}
构造函数的分类和调用
有无参数
- 无参数(默认构造)
- 有参数
按类型
- 普通构造函数
- 拷贝构造函数
class Person{
public:
Person();
Person(int age){m_age = age;}
Person(const Person& p){m_age = p.age;}
private:
int m_age;
};
void test()
{
//构造函数的调用方式
Person p1; //无参构造,默认构造函数,不允许加(),否则编译器会认为这是函数的声明
Person p2(1); //有参构造,
Person p3(p2); //拷贝构造函数
//显示调用
Person p4 = Person(40);//Person(40) 是匿名对象,这里给他了一个名字p4,如果只是Person(40);那么编译器在执行完这行代码以后就会释放这个对象。
//不能用拷贝构造函数去初始化一个匿名对象
Person(p5); //编译器会报重定义,因为他会认为你写的是Person p5;
Person p5 = Person(p4); //写在右边就可以
//隐式类型转换
Person p6 = 100; //相当于Person p6 = Person(100);
Person p7 = p6; //相当于Person p7 = Person(p6);
}
拷贝构造函数调用的时机
- 用一个对象去初始化另一个对象
- 以值传递的方式传给函数参数
- 以值方式返回局部变量
//自己调用
Person p1(p);
//以值传递的方式传给函数参数
void doWork(Person p) //实际上是Person p = Person(p2)
void test()
{
Person p2;
dowork(p2);
}
//以值方式返回局部变量
void doWork2()
{
Preson p3;
return p3; //编译器会自动拷贝一个新的对象
}
构造函数的调用规则
- 当我们提供了有参构造函数,那么系统不会给我们提供默认构造函数,但会提供默认拷贝构造函数
- 当我们提供了拷贝构造函数,那么系统不会给我们提供默认构造函数。
深拷贝和浅拷贝
系统 提供的默认拷贝构造函数只会进行简单的值拷贝,当类中有指针,且指针指向动态分配的内存,默认拷贝只会简单的进行复制,两个对象会指向同一块内存,当一个对象析构之后,另一个对象的析构就会发生内存错误。
class Person{
public:
Person(char *name, int age)
{
m_name = (char *)malloc(strlen(name) + 1);
strcpy(m_name, name);
m_age = age;
}
//默认拷贝构造函数,浅拷贝
Person(const Person& p)
{
cout << "默认拷贝构造函数" << endl;
}
//深拷贝
Person(const Person& p)
{
m_name = (char *)malloc(strlen(p.m_name) + 1);
strcpy(m_name, p.m_name);
m_age = p.m_age;
}
//析构函数
~Person()
{
if(m_name != NULL)
{
free(m_name);
m_name = NULL;
}
}
private:
char *m_name;
int m_age;
};
初始化列表
在构造函数后面加 :属性(值、参数),属性(值、参数),,,,
class Person
{
public:
Person() : m_A(10), m_B(20), m_C(30){}
Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c){}
private:
int m_A;
int m_B;
int m_C;
};
类对象作为成员
类中定义的数据成员一般都是基本的数据类型,但是类中的成员也可以是对象,叫做对象成员
当调用构造函数时,首先按各对象成员在类定义中的顺序(和参数列表的顺序无关) 依次调用它们的构造函数,对这些对象初始化,最后再调用本身的构造函数。也就是说, 先调用对象成员的构造函数,再调用本身的构造函数。
#include <iostream>
#include <string>
using namespace std;
class Phone{
public:
Phone(){cout << "手机的默认构造函数" << endl;}
Phone(string name)
{
cout << "手机的有参构造函数" << endl;
mPhoneName = name;
}
~Phone(){cout << "手机的默认析构函数" << endl;}
string mPhoneName;
};
class Game{
public:
Game(){cout << "游戏的默认构造函数" << endl;}
Game(string name)
{
cout << "游戏的有参构造函数" << endl;
mGameName = name;
}
~Game(){cout << "游戏的默认析构函数" << endl;}
string mGameName;
};
class Person{
public:
Person(){cout << "人的默认构造函数" << endl;}
Person(string name, string phoneName, string gameName) : mName(name), mPhone(phoneName), mGame(gameName)
{
cout << "人的有参构造函数" << endl;
//mName = name;
}
~Person(){cout << "人的默认析构函数" << endl;}
string mName;
Phone mPhone;
Game mGame;
};
int main()
{
Person p("张三","XIAOMI","PUBG");
return 0;
}
/*输出结果为
手机的有参构造函数
游戏的有参构造函数
人的有参构造函数
人的默认析构函数
游戏的默认析构函数
手机的默认析构函数*/
explicit
explicit 关键字的作用是禁止通过构造函数进行隐式转换
- explicit 只能用于修饰构造函数,防止隐式转换
- 只能针对单参数的构造函数(或者出了第一个参数外其余参数都有默认值的多参数构造函数)
class MyString
{
public:
explicit MyString(int n)
{
cout << "MyString(int n)!" << endl;
}
MyString(const char* str)
{
cout << "MyString(const char* str)" << endl;
}
};
int main()
{
MyString str1 = 1;//错误
//隐式转换,MyString str1 = MyString(1)或者MyString str1 = MyString(“1”)
//加上了explicit之后,禁止了这种隐式转换
MyString str2(10);//正确
//寓意非常明确,给字符串赋值
MyString str3 = "abcd";
MyString str4("abcd");
return EXIT_SUCCESS;
}
静态成员变量和静态成员函数
在一个类中,若将一个成员变量声明为static,这种成员变量成员静态成员变量。与一般的成员变量不同,无论建立多少个对象,都只有这一个静态变量,也就是说,多个对象共享一个数据
class Person{
public:
person();
//静态成员变量
static int m_age;//静态成员变量,在类内声明,在类外初始化
//静态成员函数,不可以访问普通成员变量,可以访问静态的成员变量
static void func()
{
cout << "Person::func()" << endl;
}
//普通成员函数,可以访问普通成员变量,也可以访问静态的成员变量
void myFunc();
private:
static int m_other;
}
//类外初始化
int Person::m_age = 0;
int Person::m_other = 0; //加上作用域之后编译器会认为你还是在类内初始化的
void test()
{
//通过对象访问数据
Person p1;
p1.m_age = 10;
Person p2;
p2.m_age = 20;
cout << "p1 = " << p1.m_age << endl; //20
cout << "p2 = " << p2.m_age << endl; //20 共享数据
//通过类名访问数据
cout << "通过类名访问数据" << Person::m_age << endl;
//静态成员函数的调用
p1.func();
p2.func();
Person:func();
}
C++对象模型存储
class Person
{
public:
int a; //非静态成员变量,属于对象
void func(); //非静态成员函数,不属于对象
static int b; //静态成员变量,不属于对象
static void func2();//静态成员函数,不属于对象
}
///sizof(Person) = 4;
/*
#pragma pack(1)
可以改变内存对齐的大小
*/
- 成员变量和成员属性是分开存储的
- 空类的大小为1,内部char 会维护这个地址
- 只有非静态成员变量才属于对象本身
this指针
this 指针指向被调用的成员函数所属的对象。在调用函数时,会将this指针传过去,隐藏于每个类的非静态成员函数中,永远指向当前的指针
非静态成员函数才有this指针
class Person{
//当形参和成员变量同名时
Person(int age)
{
//age = age;编译器不知道age是指哪个
this->age = age;
}
int age
//链式编程,当盛园函数需要返回当前对象时,可以返回对象的引用,return *this;
}
空指针访问成员函数
- 如果成员函数中没有用到this指针,那么空指针可以直接访问
- 如果成员函数用到了this指针,那么就要注意,可以加一个if判断
常函数和常对象
常函数
class Person
{
public:
//常函数,实际是修饰const Type * const this;
void func() const
{
//a = 100;错误,常函数内不允许修改指针的指向
b = 1000;
}
int a;
mutable int b;//如果执意要修改,在变量类型前加 mutable
}
//常对象,不允许调用普通的成员函数,只可以调用常函数
const Person p;
友元
友元函数
访问类中的私有属性
全局函数作友元函数,将声明放到类中,并在前面加friend关键字;
class Person{
public:
friend void func(Preson *p)//友元函数的声明
int a;
private:
int b;
}
void func(Preson *p)
{
cout << p->a << endl; //没问题,a是public
cout << p->b << endl; //有问题,b是private,可以将函数定义为友元函数
}
友元类
让一个类可以访问另一个类的私有数据
class Person{
friend class other; //友元类
}
other里面的成员函数访问Person的私有属性
- 友元类是不可继承的
- 友元类是单向的
- 友元类是不可传递的
成员函数作友元函数
class Person{
//让Other的成员函数作Person的友元
friend void Other::func();
}
运算符重载
- 如果想让自定义类型进行运算,系统无法识别,就需要对运算符进行重载
- 方法:在成员函数 或 全局函数 中重写一个函数,函数名为 operator+(){}
- 运算符重载可以提供多个版本
+号运算符重载
class Person
{
public:
Person(){};
Person(int a, int b) : m_A(a), m_B(b){}
//利用成员函数,完成运算符重载
Person operator+(Person &p)
{
Person tmp;
tmp.m_A = this->m_A + p.m_A;
tmp.m_B = this->m_B + p.m_B;
return tmp;
}
int m_A;
int m_B;
};
利用全局函数实现运算符重载
Person operator+(Person &p1, Person &p2)
{
Person tmp;
tmp.m_A = p1.m_A + p2.m_A;
tmp.m_B = p1.m_B + p2.m_B;
return tmp;
}
Person operator+(Person &p, int a)
{
Person tmp;
tmp.m_A = p.m_A + a;
tmp.m_B = p.m_B + a;
return tmp;
}
void test()
{
Person p1(10,20);
Person p2(20,20);
Person p3;
p3 = p2 + p1;
cout << p3.m_A << p3.m_B << endl;
p3 = p2 + 20;
cout << p3.m_A << p3.m_B << endl;
}
左移运算符重载
class Person
{
friend ostream& operator<<(ostream &cout, Person &p);
public:
Person(){};
Person(int a, int b) : m_A(a), m_B(b){}
private:
int m_A;
int m_B;
};
//返回值为cout,因为之后 << endl 不知道应该流向哪里
//左移函数不能使用成员函数实现,如果使用成员函数,那么就需要调用对象,不符合原本的书写
//因为要访问私有数据,故使用友元函数
ostream& operator<<(ostream &cout, Person &p)
{
cout << "m_A = " << p.m_A << "; m_B = " << p.m_B;
return cout;
}
void test()
{
Person p(10,20);
cout << p << endl;
}
赋值运算符重载
class Person
{
public:
Person(int a)
{
m_A = a;
}
int m_A;
};
void test01()
{
Person p1(10);
Person p2(20);
p2 = p1; //并没有进行运算符重载,但是还是能够进行肤质,是因为一个类创建时,会给出一个 operator=的函数,只会进行简单的赋值操作
cout << p1.m_A << endl;
cout << p2.m_A << endl;
}
class Person2
{
public:
Person2(char *name)
{
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
}
//进行运算符= 的重载,防止出现深拷贝浅拷贝的问题
//在创建之前,需要先判断之前是否有申请的空间,如果有,就释放
//返回本身
Person2& operator=(const Person2 &p)
{
if(this->m_name != NULL)
{
delete[] m_name;
m_name = NULL;
}
m_name = new char[strlen(p.m_name) + 1];
strcpy(m_name, p.m_name);
return *this;
}
~Person2()
{
if(this->m_name != NULL)
{
delete[] m_name;
m_name = NULL;
}
}
char *m_name;
};
void test02()
{
Person2 p1("狗剩");
Person2 p2("狗蛋");
Person2 p3("");
p3 = p2 = p1;
cout << p3.m_name << endl;
}