目录
前言
面向对象是一种开发思想,一种全新的开发模式,可以解决面向过程开发大型项目时,难以把控以及后期维护成大的问题。那么什么是面向过程呢,面向过程就是根据程序的执行过程,来设计软件的所有细节。(本文参考奇牛编程)
蒟蒻第一次写博客,如有不足,请多多指教
类和对象
1 类和对象的基本概念
1.1 类
“类”,是一种特殊的“数据类型”,不是一个具体的数据。
注意:类, 和基本数据类型(char/int/short/long/long long/double)不同
类的构成:方法和数据
类的设计
那么如何去定义一个类呢?,下面我们定义一个“人类”
class Human {
public: //公有的,对外的
void eat(); //方法, “成员函数”
int getAge();
private:
int age;
};
1.2 对象
对象,是一个特定“类”的具体实例。那么对象和普通变量有什么区别?一般地,一个对象,就是一个特殊的变量,但是有跟丰富的功能和用法,下面是对象的具体使用方法。
//方式一
int main(void) {
Human h1; // 通过自定义的特殊数据类型“Human”类, 来创建一个“对象”
h1.eat();// 合法使用 h1.eat();
//cout << "年龄: " << h1.age << endl; //直接访问私有成员,将无法通过编译
//正确使用
cout << "年龄: " << h1.getAge() << endl; //暴露问题
}
//方式二
int main(void) {
Human h1; // 通过自定义的特殊数据类型“Human”类, 来创建一个“对象”
Human *p;
p = &h1;
// 合法使用
p->eat();
// 非法使用
//cout << "年龄: " << p->age << endl; //直接访问私有成员,将无法通过编译
//正确使用
cout << "年龄: " << p->getAge() << endl;
1.3 完整代码
#include <iostream>
#include <string>
using namespace std;
class Human {
public: //公有的,对外的
void eat(); //方法, “成员函数”
int getAge();
private:
int age;
};
void Human::eat() {
cout << "吃东西" << endl;
}
int Human::getAge() {
return age;
}
int main(void) {
Human h1; // 通过自定义的特殊数据类型“Human”类, 来创建一个“对象”
h1.eat();// 合法使用 h1.eat();
//cout << "年龄:" << h1.age << endl; //直接访问私有成员,将无法通过编译
//正确使用
cout << "年龄: " << h1.getAge() << endl;
}
1.4 小结
1)调用方法时,方法名后需要带一对圆括号()
2)通过对象,只能调用这个对象的 public 方法
2 类的特性
类的三大特性:封装、继承、多态(这里主要讲类的封装,继承和多态后面会详讲)
2.1 封装的概念
简单来说,就是把类的属性和方法进行访问权限控制,来实现封装
访问权限
1) 在类的内部,所有成员可以相互访问
2) 在类的外部,访问权限才有意义(public,private,protected)
3) 在类的外部,只有public修饰的成员才能被访问,在没有涉及继承与派生时,private和protected是同等级的,外部不允许访问
详见下方的表格
访问属性 | 属性 | 对象内部 | 对象外部 |
---|---|---|---|
public | 公有 | 可访问 | 可访问 |
protected | 保护 | 可访问 | 不可访问 |
private | 私有 | 可访问 | 不可访问 |
2.2 代码实现
#include <iostream>
#include <string>
using namespace std;
class Human {
//人具有的行为(方法)
public: //公有的,对外的
void eat(); //方法, “成员函数”
//人具有的属性(变量)
public:
int getAge();//外人可以知道
protected:
int salary;//只有儿子孙子才能知道
private:
int age; //只有自己才能知道
};
void Human::eat() {
cout << "吃东西" << endl;
}
int Human::getAge() {
return age;
}
int main(void) {
Human h1; // 通过自定义的特殊数据类型“Human”类, 来创建一个“对象”
//h1.salary;保护成员外部无法访问
h1.eat();// 合法使用 h1.eat();
//h1.age ; //私有成员外部无法访问
//正确使用
cout << "年龄: " << h1.getAge() << endl;
}
3 类的构造和析构
类的构造和析构是什么呢?下面让我给大家举个例子
譬如说我们大家在购买手机、电脑、平板等电子产品的时候,拿到手的时候,里面已经装好一些东西了,那这些就是所购买电子产品的初始信息,那当我们使用一段时间后,这些电子产品里面有很多我们新增进去的信息,并且当我们以后不去使用这些产品的时候,我们应该把这些自己新增信息给删掉,以此来保护自己隐私。
那么在c++中,当我们创建对象的时候,这个对象应该有一个初始状态,当对象销毁之前应该销毁自己创建的一些数据,以此来保证程序的安全,所以c++为我们提供了构造函数和析构函数,这两个函数将会被编译器自动调用,完成对象初始化和对象清理工作。
大家看到这里应该对构造和析构有了一个大概的概念吧,下面正片开始!
3.1 构造函数的特点
1)自动调用(在创建新对象时,自动调用)
2)构造函数的函数名,和类名相同
3)构造函数没有返回类型
4)可以有多个构造函数(即函数重载形式)
3.2 构造函数的种类
构造函数有默认构造函数、自定义构造函数、拷贝构造函数和赋值构造函数
3.2.1 合成默认构造函数
没有参数的构造函数,称为默认构造函数。当我们没有手动定义默认构造函数时,编译器自动为这个类定义一个构造函数,我们称之为合成默认构造函数。
1)如果数据成员使用了“类内初始值”,就用这个值初始化数据成员。(仅在c++11中支持)
2)否则,就使用默认初始化(实际上,不做任何初始化)
见如下参考代码
#include <iostream>
#include <string>
using namespace std;
class Human {
//人具有的行为(方法)
public: //公有的,对外的
void eat(); //方法, “成员函数”
int getAge();
int getSalary();
private: //私有的
int age = 18;//仅在c++11支持
int salary;
};
void Human::eat() {
cout << "吃东西" << endl;
}
int Human::getAge() {
return age;
}
int Human::getSalary()
{
return salary;
}
int main(void) {
Human h1; // 使用了合成的默认构造函数
cout << "年龄: " << h1.getAge() << endl;//使用类内初始值
cout << "薪资: " << h1.getSalary() << endl;//没有类内初始值
return 0;
}
输出如下:
注意: 只要手动定义了任何一个构造函数,编译器就不会生成“合成的默认构造函数” 一般情况下,都应该定义自己的构造函数,不要使用“合成的默认构造函数” (仅当数据成员全部使用了“类内初始值”,才宜使用“合成的默认构造函数”)
3.2.2 手动定义的默认构造函数
也是默认构造函数的一种,参考代码如下
#include <iostream>
#include <string>
using namespace std;
class Human {
//人具有的行为(方法)
public: //公有的,对外的
Human(); //手动定义的“默认构造函数”
void eat(); //方法, “成员函数”
int getAge();
int getSalary();
private:
int age = 18;//仅在c++11支持
int salary;
};
Human::Human() {
age = 28;
salary = 30000;
}
void Human::eat() {
cout << "吃东西" << endl;
}
int Human::getAge() {
return age;
}
int Human::getSalary()
{
return salary;
}
int main(void) {
Human h1; // 使用了自定义的默认构造函数
cout << "年龄: " << h1.getAge() << endl;//使用类内初始值
cout << "薪资: " << h1.getSalary() << endl;//没有类内初始值
return 0;
}
注意:如果某数据成员使用类内初始值,同时又在构造函数中进行了初始化, 那么以构造函数中的初始化为准(即相当于构造函数中的初始化,会覆盖对应的类内初始值)
3.2.3 自定义重载构造函数
代码如下:
#include <iostream>
#include <string>
using namespace std;
class Human {
//人具有的行为(方法)
public: //公有的,对外的
Human(); //手动定义的“默认构造函数”
Human(int age,int salary);
void eat(); //方法, “成员函数”
int getAge();
int getSalary();
private:
int age = 18;//仅在c++11支持
int salary;
};
Human::Human() {
age = 28;
salary = 30000;
}
Human::Human(int age, int salary) {
cout << "调用自定义的构造函数" << endl;
this->age = age; //this 是一个特殊的指针,指向这个对象本身
this->salary = salary;
}
void Human::eat() {
cout << "吃东西" << endl;
}
int Human::getAge() {
return age;
}
int Human::getSalary()
{
return salary;
}
int main(void) {
Human h1(25,35000); // 使用自定义的默认构造函数
cout << "年龄: " << h1.getAge() << endl;//使用类内初始值
cout << "薪资: " << h1.getSalary() << endl;//没有类内初始值
return 0;
}
3.2.4 拷贝构造函数
这里的知识就直接上代码演示了
合成的拷贝构造函数
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
class Human {
//人具有的行为(方法)
public: //公有的,对外的
Human(); //手动定义的“默认构造函数”
Human(int age,int salary);
void eat(); //方法, “成员函数”
int getAge();
int getSalary();
void setAddr(const char *newAddr);
const char* getAddr();
private:
int age = 18;//仅在c++11支持
int salary;
char *addr;
};
Human::Human() {
age = 28;
salary = 30000;
}
Human::Human(int age, int salary) {
cout << "调用自定义的构造函数" << endl;
this->age = age; //this 是一个特殊的指针,指向这个对象本身
this->salary = salary;
addr = new char[64];
strcpy_s(addr, 64, "China");
}
void Human::eat() {
cout << "吃东西" << endl;
}
int Human::getAge() {
return age;
}
int Human::getSalary()
{
return salary;
}
void Human::setAddr(const char *newAddr) {
if (!newAddr) {
return;
}
strcpy_s(addr, 64, newAddr);
}
const char* Human::getAddr() {
return addr;
}
int main(void) {
Human h1(25,35000); // 使用自定义的默认构造函数
Human h2(h1);
cout << "h1 addr:" << h1.getAddr() << endl;
cout << "h2 addr:" << h2.getAddr() << endl;
h1.setAddr("长沙");
cout << "h1 addr:" << h1.getAddr() << endl;
cout << "h2 addr:" << h2.getAddr() << endl;
return 0;
}
输出结果如下:
那么这里大家就能看出问题了,明明只修改了h1,为什么h2也跟着h1变了呢?
这是由于合成的拷贝构造函数是“浅拷贝”,打个比方比如说张三有个女朋友如花,年龄是xx,然后我们把张三拷贝过来,变成了李四,那么最终李四女朋友也会指向如花,这是万万不可以的!!!会出现很大的问题,该过程详见下图
那么出现这样的问题,我们该如何解决呢?这里就要用到"深拷贝",即我们应该自己定义一个拷贝构造函数。
自定义的拷贝构造函数
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
class Human {
//人具有的行为(方法)
public: //公有的,对外的
Human(); //手动定义的“默认构造函数”
Human(int age,int salary);
Human(const Human&); //不定义拷贝构造函数,编译器会生成“合成的拷贝构造函数
void eat(); //方法, “成员函数”
int getAge();
int getSalary();
void setAddr(const char *newAddr);
const char* getAddr();
private:
int age = 18;//仅在c++11支持
int salary;
char *addr;
};
Human::Human() {
age = 28;
salary = 30000;
}
Human::Human(int age, int salary) {
cout << "调用自定义的构造函数" << endl;
this->age = age; //this 是一个特殊的指针,指向这个对象本身
this->salary = salary;
addr = new char[64];
strcpy_s(addr, 64, "China");
}
Human::Human(const Human &man) {
cout << "调用自定义的拷贝构造函数" << endl;
age = man.age; //this 是一个特殊的指针,指向这个对象本身
salary = man.salary;
// 深度拷贝
addr = new char[64];
strcpy_s(addr, 64, man.addr);
}
void Human::eat() {
cout << "吃东西" << endl;
}
int Human::getAge() {
return age;
}
int Human::getSalary()
{
return salary;
}
void Human::setAddr(const char *newAddr) {
if (!newAddr) {
return;
}
strcpy_s(addr, 64, newAddr);
}
const char* Human::getAddr() {
return addr;
}
int main(void) {
Human h1(25,35000); // 使用自定义的默认构造函数
Human h2(h1); //调用自定义的拷贝构造函数
cout << "h1 addr:" << h1.getAddr() << endl;
cout << "h2 addr:" << h2.getAddr() << endl;
h1.setAddr("长沙");
cout << "h1 addr:" << h1.getAddr() << endl;
cout << "h2 addr:" << h2.getAddr() << endl;
return 0;
}
结果如下:
3.2.5 赋值构造函数
#include <iostream>
#include <Windows.h>
#include <string>
#include <string.h>
using namespace std;
// 定义一个“人类”
class Human {
public:
Human();
Human(int age, int salary);
Human(const Human&); //不定义拷贝构造函数,编译器会生成“合成的拷贝构造函数”
Human& operator=(const Human &);
int getAge();
int getSalary();
private:
int age;
int salary;
};
Human::Human() {
age = 18;
salary = 30000;
}
Human::Human(int age, int salary) {
cout << "调用自定义的构造函数" << endl;
this->age = age; //this 是一个特殊的指针,指向这个对象本身
this->salary = salary;
}
Human::Human(const Human &man) {
cout << "调用自定义的拷贝构造函数" << "参数:" << &man
<< " 本对象:" << this << endl;
age = man.age; //this 是一个特殊的指针,指向这个对象本身
salary = man.salary;
}
Human& Human::operator=(const Human &man) {
cout << "调用" << __FUNCTION__ << endl;
if (this == &man) {
return *this; //检测是不是对自己赋值:比如 h1 = h1;
}
// 处理其他数据成员
age = man.age;
salary = man.salary;
// 返回该对象本身的引用, 以便做链式连续处理,比如 a = b = c;
return *this;
}
int Human::getAge() {
return age;
}
int Human::getSalary() {
return salary;
}
void test(Human man) {
cout << man.getSalary() << endl;
}
void test2(Human &man) {
//不会调用拷贝构造函数,此时没有没有构造新的对象
cout << man.getSalary() << endl;
}
Human test3(Human &man) {
return man;
}
Human& test4(Human &man) {
return man;
}
int main() {
Human h1(25, 35000); // 调用默认构造函数
// 特别注意,此时是创建对象 h2 并进行初始化,调用的是拷贝构造函数
// 不会调用赋值构造函数
Human h2 = h1;
h2 = h1; //调用赋值构造函数
h2 = test3(h1); //调用赋值构造函数
Human h3 = test3(h1); //调用拷贝构造函数
system("pause");
return 0;
}
注意:如果没有定义赋值构造函数,编译器会自动定义“合成的赋值构造函数”, 与其他合成的构造函数,是“浅拷贝”
3.2.6 拷贝构造函数和赋值构造函数的区别
通过上述代码对比发现,拷贝构造函数是将对象的值传入到一个新的对象,而赋值构造函数是通过赋值运算符将对象的值赋值给一个已经存在的对象。
3.3 拷贝构造函数的调用时机
- 调用函数时,实参是对象,形参不是引用类型 如果函数的形参是引用类型,就不会调用拷贝构造函数
- 函数的返回类型是类,而且不是引用类型
- 对象数组的初始化列表中,使用对象。
详见下方代码
#include <iostream>
#include <Windows.h>
#include <string>
#include <string.h>
using namespace std;
// 定义一个“人类”
class Human {
public:
Human();
Human(int age, int salary);
Human(const Human&); //不定义拷贝构造函数,编译器会生成“合成的拷贝构造函数”
int getAge();
int getSalary();
private:
int age;
int salary;
};
Human::Human() {
age = 18;
salary = 30000;
}
Human::Human(int age, int salary) {
cout << "调用自定义的构造函数" << endl;
this->age = age; //this 是一个特殊的指针,指向这个对象本身
this->salary = salary;
}
Human::Human(const Human &man) {
cout << "调用自定义的拷贝构造函数" << "参数:" << &man
<< " 本对象:" << this << endl;
age = man.age; //this 是一个特殊的指针,指向这个对象本身
salary = man.salary;
}
int Human::getAge() {
return age;
}
int Human::getSalary() {
return salary;
}
void test(Human man) {
cout << man.getSalary() << endl;
}
void test2(Human &man) {
//不会调用拷贝构造函数,此时没有没有构造新的对象
cout << man.getSalary() << endl;
}
Human test3(Human &man) {
return man;
}
Human& test4(Human &man) {
return man;
}
int main() {
Human h1(25, 35000); // 调用默认构造函数
Human h2(h1); // 调用拷贝构造函数
Human h3 = h1; // 调用拷贝构造函数
test(h1); // 调用拷贝构造函数
test2(h1); // 不会调用拷贝构造函数
test3(h1); // 创建一个临时对象,接收 test3 函数的返回值,调用 1 次拷贝构造函数
Human h4 = test3(h1); // 仅调用 1 次拷贝构造函数,返回的值直接作为 h4 的拷贝构造函数的参数
test4(h1); // 因为返回的是引用类型,所以不会创建临时对象,不会调用拷贝构造函数
Human men[] = { h1, h2, h3 }; //调用 3 次拷贝构造函数
system("pause");
return 0;
}
3.4 析构函数
析构函数是在对象销毁前,做清理工作的。具体的清理工作,一般和构造函数对应,比如:如果在构造函数中,使用 new 分配了内存,就需在析构函数中用 delete 释放。
如果构造函数中没有申请资源(主要是内存资源),那么很少使用析构函数。
函数名: ~类型 没有返回值,没有参数,最多只能有一个析构函数
访问权限: 一般都使用 public
使用方法: 不能主动调用。对象销毁时,自动调用。如果不定义,编译器会自动生成一个析构函数(什么也不做)
代码如下
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
// 定义一个“人类”
class Human {
public: Human();
Human(int age, int salary);
Human(const Human&); //不定义拷贝构造函数,编译器会生成“合成的拷贝构造函数”
Human& operator=(const Human &);
~Human(); //析构函数 ......
private:
int age ;
int salary;
char *addr;
};
Human::Human() {
age = 18;
salary = 30000;
addr = new char[64];
strcpy_s(addr, 64, "China");
cout << "调用默认构造函数-" << this << endl;
}
Human::~Human() {
cout << "调用析构函数-" << this << endl; //用于打印测试信息
delete addr;
}
void test() {
Human h1;
{
Human h2;
}
cout << "test()结束" << endl;
}
int main(void) {
test();
system("pause");
return 0;
}