C++面向对象
面向对象的思想
面向过程:
根据程序的执行过程,来设计软件的所有细节。面向过程的缺点:开发大型项目时,越来越难以把控,甚至失去控制。后期维护、更新成本很大。解决方案:使用面向对象。
什么是面向对象?
不是面向对象,写代码:面向对象是一种开发思想,一种全新的开发方式。面向对象思想的重要性:开发大型项目必备,是高级程序员的必备技能!
类的使用
面向对象编程,最重要的第一个概念:类。“人类”是一个抽象的概念,不是具体的某个人。“类”,是看不见,摸不着的,是一个纯粹的概念。“类”,是一种特殊的“数据类型”,不是一个具体的数据。注意:类, 和基本数据类型(char/int/short/long/long long/float/double)不同。
类的构成:方法和数据
类的设计
对象的基本使用
对象,是一个特定“类”的具体实例。对象和普通变量的区别:一般地,一个对象,就是一个特殊的变量,但是有跟丰富的功能和用法。
方式一
#include <iostream>
//#include <Windows.h>
#include <string>using namespace std;// 定义一个“人类”
class Human {
public: //公有的,对外的void eat(); //方法, “成员函数”void sleep();void play();void work();string getName();int getAge();int getSalary();private:string name;int age;int salary;
};void Human::eat() {cout << "吃炸鸡,喝啤酒!" << endl;
}void Human::sleep() {cout << "我正在睡觉!" << endl;
}void Human::play() {cout << "我在唱歌! " << endl;
}void Human::work() {cout << "我在工作..." << endl;
}string Human::getName() {return name;
}int Human::getAge() {return age;
}int Human::getSalary() {return salary;
}int main(void) {Human h1; // 通过自定义的特殊数据类型“Human”类, 来创建一个“对象”// 合法使用h1.eat();h1.play();h1.sleep();// 非法使用//cout << "年龄" << h1.age << endl; //直接访问私有成员,将无法通过编译//正确使用cout << "年龄" << h1.getAge() << endl; //暴露问题,年龄值是一个很大的负数//system("pause");
}
总结:“.”的使用、调用方法时,方法名后需要带一对圆括号()、通过对象,只能调用这个对象的public方法
分析:多个不同的对象都有自己的数据,彼此无关。
方式2
int main(void) {Human h1; // 通过自定义的特殊数据类型“Human”类, 来创建一个“对象”Human *p;p = &h1;// 合法使用p->eat();p->play();p->sleep();// 非法使用//cout << "年龄" << p->age << endl; //直接访问私有成员,将无法通过编译//正确使用cout << "年龄" << p->getAge() << endl; //暴露问题,年龄值是一个很大的负数system("pause");
}
小结:-> 的使用(类似C语言的结构体用法)、
构造函数
构造函数的作用:在创建一个新的对象时,自动调用的函数,用来进行“初始化”工作:对这个对象内部的数据成员进行初始化。
构造函数的特点:自动调用(在创建新对象时,自动调用)、构造函数的函数名,和类名相同、
-
构造函数没有返回类型
-
可以有多个构造函数(即函数重载形式)
构造函数的种类
默认构造函数、自定义的构造函数、拷贝构造函数、赋值构造函数
默认构造函数
没有参数的构造函数,称为默认构造函数。
合成的默认构造函数
没有手动定义默认构造函数时,编译器自动为这个类定义一个构造函数。如果数据成员使用了“类内初始值”,就使用这个值来初始化数据成员【C++11】。否则,就使用默认初始化(实际上,不做任何初始化)。
#include <iostream>
#include <Windows.h>
#include <string>using namespace std;// 定义一个“人类”
class Human {
public: //公有的,对外的void eat(); //方法, “成员函数”void sleep();void play();void work();string getName();int getAge();int getSalary();
private:string name;int age = 18;int salary;
};void Human::eat() {cout << "吃炸鸡,喝啤酒!" << endl;
}void Human::sleep() {cout << "我正在睡觉!" << endl;
}void Human::play() {cout << "我在唱歌! " << endl;
}void Human::work() {cout << "我在工作..." << endl;
}string Human::getName() {return name;
}int Human::getAge() {return age;
}int Human::getSalary() {return salary;
}int main(void) {Human h1; // 使用合成的默认初始化构造函数cout << "年龄: " << h1.getAge() << endl; //使用了类内初始值cout << "薪资:" << h1.getSalary() << endl; //没有类内初始值system("pause");return 0;
}
注意:只要手动定义了任何一个构造函数,编译器就不会生成“合成的默认构造函数”。一般情况下,都应该定义自己的构造函数,不要使用“合成的默认构造函数”【仅当数据成员全部使用了“类内初始值”,才宜使用“合成的默认构造函数”】。
手动定义的默认构造函数
常称为“默认构造函数”
#include <iostream>
#include <Windows.h>
#include <string>using namespace std;// 定义一个“人类”
class Human {
public: //公有的,对外的Human(); //手动定义的“默认构造函数”void eat(); //方法, “成员函数”void sleep();void play();void work();string getName();int getAge();int getSalary();
private:string name = "Unknown";int age = 28;int salary;
};Human::Human() {name = "无名氏";age = 18;salary = 30000;
}void Human::eat() {cout << "吃炸鸡,喝啤酒!" << endl;
}void Human::sleep() {cout << "我正在睡觉!" << endl;
}void Human::play() {cout << "我在唱歌! " << endl;
}void Human::work() {cout << "我在工作..." << endl;
}string Human::getName() {return name;
}int Human::getAge() {return age;
}int Human::getSalary() {return salary;
}int main(void) {Human h1; // 使用自定义的默认构造函数cout << "姓名:" << h1.getName() << endl;cout << "年龄: " << h1.getAge() << endl; cout << "薪资:" << h1.getSalary() << endl;system("pause");return 0;
}
说明:如果某数据成员使用类内初始值,同时又在构造函数中进行了初始化,那么以构造函数中的初始化为准。相当于构造函数中的初始化,会覆盖对应的类内初始值。
自定义的重载构造函数
#include <iostream>
#include <Windows.h>
#include <string>
using namespace std;// 定义一个“人类”
class Human {
public: Human();Human(int age, int salary);void eat();void sleep();void play();void work();string getName();int getAge();int getSalary();
private:string name = "Unknown";int age = 28;int salary;
};Human::Human() {name = "无名氏";age = 18;salary = 30000;
}Human::Human(int age, int salary) {cout << "调用自定义的构造函数" << endl;this->age = age; //this是一个特殊的指针,指向这个对象本身this->salary = salary;name = "无名";
}void Human::eat() {cout << "吃炸鸡,喝啤酒!" << endl;
}void Human::sleep() {cout << "我正在睡觉!" << endl;
}void Human::play() {cout << "我在唱歌! " << endl;
}void Human::work() {cout << "我在工作..." << endl;
}string Human::getName() {return name;
}int Human::getAge() {return age;
}int Human::getSalary() {return salary;
}int main(void) {Human h1(25, 35000); // 使用自定义的默认构造函数cout << "姓名:" << h1.getName() << endl;cout << "年龄: " << h1.getAge() << endl; cout << "薪资:" << h1.getSalary() << endl;system("pause");return 0;
}
拷贝构造函数
手动定义的拷贝构造函数
#include <iostream>
#include <Windows.h>
#include <string>using namespace std;
// 定义一个“人类”
class Human {
public: Human();Human(int age, int salary);Human(const Human&);void eat();void sleep();void play();void work();string getName();int getAge();int getSalary();
private:string name = "Unknown";int age = 28;int salary;
};Human::Human() {name = "无名氏";age = 18;salary = 30000;
}Human::Human(int age, int salary) {cout << "调用自定义的构造函数" << endl;this->age = age; //this是一个特殊的指针,指向这个对象本身this->salary = salary;name = "无名";
}Human::Human(const Human& man) {cout << "调用自定义的拷贝构造函数" << endl;name = man.name;age = man.age;salary = man.salary;
}void Human::eat() {cout << "吃炸鸡,喝啤酒!" << endl;
}void Human::sleep() {cout << "我正在睡觉!" << endl;
}void Human::play() {cout << "我在唱歌! " << endl;
}void Human::work() {cout << "我在工作..." << endl;
}string Human::getName() {return name;
}int Human::getAge() {return age;
}int Human::getSalary() {return salary;
}int main(void) {Human h1(25, 35000); // 使用自定义的默认构造函数Human h2(h1); // 使用自定义的拷贝构造函数cout << "姓名:" << h2.getName() << endl;cout << "年龄: " << h2.getAge() << endl; cout << "薪资:" << h2.getSalary() << endl;system("pause");return 0;
}
合成的拷贝构造函数
#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&); //不定义拷贝构造函数,编译器会生成“合成的拷贝构造函数”void eat();void sleep();void play();void work();string getName();int getAge();int getSalary();void setAddr(const char *newAddr);const char* getAddr();
private:string name = "Unknown";int age = 28;int salary;char *addr;
};Human::Human() {name = "无名氏";age = 18;salary = 30000;
}Human::Human(int age, int salary) {cout << "调用自定义的构造函数" << endl;this->age = age; //this是一个特殊的指针,指向这个对象本身this->salary = salary;name = "无名";addr = new char[64];strcpy_s(addr, 64, "China");
}void Human::eat() {cout << "吃炸鸡,喝啤酒!" << endl;
}void Human::sleep() {cout << "我正在睡觉!" << endl;
}void Human::play() {cout << "我在唱歌! " << endl;
}void Human::work() {cout << "我在工作..." << endl;
}string Human::getName() {return name;
}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;system("pause");return 0;
}
说明:合成的拷贝构造函数的缺点: 使用“浅拷贝”。解决方案:在自定义的拷贝构造函数中,使用‘深拷贝
#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&); //不定义拷贝构造函数,编译器会生成“合成的拷贝构造函数”void eat();void sleep();void play();void work();string getName();int getAge();int getSalary();void setAddr(const char *newAddr);const char* getAddr();
private:string name = "Unknown";int age = 28;int salary;char *addr;
};Human::Human() {name = "无名氏";age = 18;salary = 30000;
}Human::Human(int age, int salary) {cout << "调用自定义的构造函数" << endl;this->age = age; //this是一个特殊的指针,指向这个对象本身this->salary = salary;name = "无名";addr = new char[64];strcpy_s(addr, 64, "China");
}Human::Human(const Human &man) {cout << "调用自定义的拷贝构造函数" << endl;age = man.age; //this是一个特殊的指针,指向这个对象本身salary = man.salary;name = man.name;// 深度拷贝addr = new char[64];strcpy_s(addr, 64, man.addr);
}void Human::eat() {cout << "吃炸鸡,喝啤酒!" << endl;
}void Human::sleep() {cout << "我正在睡觉!" << endl;
}void Human::play() {cout << "我在唱歌! " << endl;
}void Human::work() {cout << "我在工作..." << endl;
}string Human::getName() {return name;
}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;system("pause");return 0;
}
什么时候调用拷贝构造函数
调用函数时,实参是对象,形参不是引用类型。如果函数的形参是引用类型,就不会调用拷贝构造函数。函数的返回类型是类,而且不是引用类型,对象数组的初始化列表中,使用对象。
#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&); //不定义拷贝构造函数,编译器会生成“合成的拷贝构造函数”void eat();void sleep();void play();void work();string getName();int getAge();int getSalary();void setAddr(const char *newAddr);const char* getAddr();
private:string name = "Unknown";int age = 28;int salary;char *addr;
};Human::Human() {name = "无名氏";age = 18;salary = 30000;
}Human::Human(int age, int salary) {cout << "调用自定义的构造函数" << endl;this->age = age; //this是一个特殊的指针,指向这个对象本身this->salary = salary;name = "无名";addr = new char[64];strcpy_s(addr, 64, "China");
}Human::Human(const Human &man) {cout << "调用自定义的拷贝构造函数" << "参数:" << &man << " 本对象:" << this << endl;age = man.age; //this是一个特殊的指针,指向这个对象本身salary = man.salary;name = man.name;// 深度拷贝addr = new char[64];strcpy_s(addr, 64, man.addr);
}void Human::eat() {cout << "吃炸鸡,喝啤酒!" << endl;
}void Human::sleep() {cout << "我正在睡觉!" << endl;
}void Human::play() {cout << "我在唱歌! " << endl;
}void Human::work() {cout << "我在工作..." << endl;
}string Human::getName() {return name;
}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;
}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(void) {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;
}
赋值构造函数
#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 &);void eat();void sleep();void play();void work();string getName();int getAge();int getSalary();void setAddr(const char *newAddr);const char* getAddr();
private:string name = "Unknown";int age = 28;int salary;char *addr;
};Human::Human() {name = "无名氏";age = 18;salary = 30000;
}Human::Human(int age, int salary) {cout << "调用自定义的构造函数" << endl;this->age = age; //this是一个特殊的指针,指向这个对象本身this->salary = salary;name = "无名";addr = new char[64];strcpy_s(addr, 64, "China");
}Human::Human(const Human &man) {cout << "调用自定义的拷贝构造函数" << "参数:" << &man << " 本对象:" << this << endl;age = man.age; //this是一个特殊的指针,指向这个对象本身salary = man.salary;name = man.name;// 深度拷贝addr = new char[64];strcpy_s(addr, 64, man.addr);
}Human& Human::operator=(const Human &man) {cout << "调用" << __FUNCTION__ << endl;if (this == &man) {return *this; //检测是不是对自己赋值:比如 h1 = h1;}// 如果有必要,需要先释放自己的资源(动态内存)//delete addr;//addr = new char[ADDR_LEN];// 深拷贝strcpy_s(addr, ADDR_LEN, other.addr);// 处理其他数据成员name = man.name;age = man.age;salary = man.salary;// 返回该对象本身的引用, 以便做链式连续处理,比如 a = b = c;return *this;
}void Human::eat() {cout << "吃炸鸡,喝啤酒!" << endl;
}void Human::sleep() {cout << "我正在睡觉!" << endl;
}void Human::play() {cout << "我在唱歌! " << endl;
}void Human::work() {cout << "我在工作..." << endl;
}string Human::getName() {return name;
}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;
}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(void) {Human h1(25, 35000); // 调用默认构造函数// 特别注意,此时是创建对象h2并进行初始化,调用的是拷贝构造函数,// 不会调用赋值构造函数Human h2 = h1; h2 = h1; //调用赋值构造函数h2 = test3(h1); //调用赋值构造函数Human h3 = test3(h1); //调用拷贝构造函数system("pause");return 0;
}
如果没有定义赋值构造函数,编译器会自动定义“合成的赋值构造函数”,与其他合成的构造函数,是“浅拷贝”(又称为“位拷贝”)。
析构函数
作用:对象销毁前,做清理工作。具体的清理工作,一般和构造函数对应。比如:如果在构造函数中,使用new分配了内存,就需在析构函数中用delete释放。如果构造函数中没有申请资源(主要是内存资源),那么很少使用析构函数。
函数名:~类型 没有返回值,没有参数,最多只能有一个析构函数
访问权限:一般都使用public
使用方法:不能主动调用。对象销毁时,自动调用。如果不定义,编译器会自动生成一个析构函数(什么也不做)
#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 &); ~Human(); //析构函数 ...... private: string name = "Unknown"; int age = 28; int salary; char *addr; }; Human::Human() { name = "无名氏"; 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; } |
this指针:永不迷失的真爱
Human::Human(int age, int salary) { cout << "调用自定义的构造函数" << endl; this->age = age; //this是一个特殊的指针,指向这个对象本身 this->salary = salary; name = "无名"; addr = new char[64]; strcpy_s(addr, 64, "China"); } |
说明:在类的静态成员函数【后续学习】中,不能使用this指针!
#include <iostream> #include <Windows.h> #include <string> #include <string.h> using namespace std; // 定义一个“人类” class Human { public: Human(); Human(int age, int salary); ...... int getAge() const; const Human* compare1(const Human *); private: string name = "Unknown"; int age = 28; int salary; char *addr; }; int Human::getAge() const { return age; } const Human* Human::compare1(const Human * other) { if (age > other->age) { return this; //没有创建新的对象 } else { return other; } } int main(void) { Human h1(25, 30000); Human h2(18, 8000); cout << h1.compare1(&h2)->getAge() << endl; system("pause"); return 0; } |
...... public: Human(); Human(int age, int salary); int getAge() const; const Human* compare1(const Human *); const Human& compare2(const Human&); private: string name = "Unknown"; int age = 28; int salary; char *addr; }; ...... const Human& Human::compare2(const Human& other) { if (age > other.age) { return *this; //访问该对象本身的引用,而不是创建一个新的对象 } else { return other; } } int main(void) { Human h1(25, 30000); Human h2(18, 8000); cout << h1.compare2(h2).getAge() << endl; system("pause"); return 0; } |
this不能指向其他对象,堪称“永不迷失的真爱”
class Human { public: Human(); Human(int age, int salary); ...... void thisTestError(Human *other) { this = other; // 将报错! } ...... }; |
类文件的分离
实际开发中,类的定义保存在头文件中,比如Human.h【类的声明文件】(C++PrimerPlus),类的成员函数的具体实现,保存在.cpp文件中,比如Human.cpp【类的方法文件】(C++PrimerPlus)。其他文件,如果需要使用这个类,就包含这个类的头文件。
静态数据成员:大众情人
需求分析:需要获取总的人数,如何实现?只能使用一个全局变量,然后在构造函数中对这个全局变量进行修改(加1)缺点:使用全局变量不方便,破坏程序的封装性。
解决方案:使用类的静态成员。
定义:Human.h
class Human { public: ...... int getCount(); private: string name = "Unknown"; int age = 28; ...... // 类的静态成员 static int count; }; |
初始化:Human.cpp
#include "Human.h" // 初始化类的静态成员 int Human::count = 0; ...... Human::Human() { cout << "调用构造函数:" << this << endl; name = "无名氏"; age = 18; salary = 30000; addr = new char[ADDR_LEN]; strcpy_s(addr, ADDR_LEN, "China"); count++; } // 类的普通成员函数,可以直接访问静态成员(可读可写) int Human::getCount() { return count; } |
main.cpp
#include "Human.h" int main(void) { Human h1; cout << h1.getCount() << endl; Human h2; cout << h1.getCount() << endl; system("pause"); return 0; } |
对于非const的类静态成员,只能在类的实现文件中初始化。const类静态成员,可以在类内设置初始值,也可以在类的实现文件中设置初始值。(但是不要同时在这两个地方初始化,只能初始化1次)。
静态成员函数:不能拥有的方法
上一节getCount的讨论:当需要获取总的人数时,还必须通过一个对象来访问,比如h1.getCount().如果当前没有可用的对象时,就非常尴尬,不能访问getCount()!
void test() { cout << "总人数: "; // ??? 没有可用的对象来访问getCount() } |
如果为了访问总的人数,而特意去创建一个对象,就很不方便,而且得到的总人数还不真实(包含了一个没有实际用处的人)。解决方案:把getCount()方法定义为类的静态方法!
类的静态方法:可以直接通过类来访问【更常用】,也可以通过对象(实例)来访问。在类的静态方法中,不能访问普通数据成员和普通成员函数(对象的数据成员和成员函数)
Human.h
#pragma once ...... class Human { public: ...... static int getCount(); ...... }; |
Human.cpp
...... //静态方法的实现,不能加static int Human::getCount() { // 静态方法中,不能访问实例成员(普通的数据成员) // cout << age; // 静态方法中,不能访问this指针 // 因为this指针是属于实例对象的 // cout << this; //静态方法中,只能访问静态数据成员 return count; } ...... |
main.cpp
void test() { cout << "总人数: "; // ??? 没有可用的对象来访问getCount() // 直接通过类名来访问静态方法! // 用法:类名::静态方法 cout << Human::getCount(); } int main(void) { Human h1, h2; test(); system("pause"); return 0; } |
说明:
静态数据成员
对象的成员函数(没有static的成员函数)内部,可以直接访问“静态数据成员”。类的静态成员函数(有static的成员函数)内部,可以直接访问“静态数据成员”。即:所有的成员函数,都可以访问静态数据成员。类不能直接访问普通的静态数据成员(Human::humanCount 非法)
静态成员函数
对象可以直接访问静态成员函数。类可以直接访问静态成员函数(Human::getHumanCount())。在类的静态成员函数(类的静态方法)内部,不能直接访问this指针和对象的数据成员!在类的静态成员函数(类的静态方法)内部,只能访问类的数据成员
const数据成员
需求分析:怎样表示人的“血型”?血型可以修改吗?
解决方案:把血型定义为const数据类型(常量数据成员)
const数据成员的初始化方式:使用类内值(C++11支持)、使用构造函数的初始化列表(如果同时使用这两种方式,以初始化列表中的值为最终初始化结果)
注意: 不能在构造函数或其他成员函数内,对const成员赋值!
Human.h
#pragma once ...... class Human { public: ...... private: ...... const string bloodType; }; |
Human.cpp
// 使用初始化列表,对const数据成员初始化 Human::Human():bloodType("未知") { ...... //在成员函数内,不能对const数据成员赋值 //bloodType = "未知血型"; count++; } void Human::description() const { cout << "age:" << age << " name:" << name << " salary:" << salary << " addr:" << addr << " bloodType:" << bloodType << endl; //其他成员函数可以“读”const变量 } |
Main.cpp
int main(void) { Human h1; h1.description(); system("pause"); return 0; } |
const成员函数
需求分析:const的Human对象,不能调用普通的成员函数。
分析:C++认为,const(常量)对象,如果允许去调用普通的成员函数,而这个成员函数内部可能会修改这个对象的数据成员!而这讲导致const对象不再是const对象!
【类比】:专一男就是const对象,撩妹方法,就是普通的成员函数,如果允许专一男调去撩妹,那么专一男,也就不专一了!
解决方案:如果一个成员函数内部,不会修改任何数据成员,就把它定义为const成员函数。
Human的description方法
//Human.h class Human { public: ...... void description() const; //注意,const的位置 ...... }; //Human.cpp void Human::description ()const { cout << "age:" << age << " name:" << name << " salary:" << salary << " addr:" << addr << " bloodType:" << bloodType << endl; } //main.cpp int main(void) { const Human h1; h1.description(); system("pause"); return 0; } |
const成员函数内,不能修改任何数据成员!
C++的成员函数设置建议:如果一个对象的成员函数,不会修改任何数据成员,那么就强烈:把这个成员函数,定义为const成员函数!
建模的常用手段:组合与聚合
说明:组合和聚合,不是C++的语法要求,是应用中的常用手段。
组合
需求:构建一个计算机类,一台计算机,由CPU芯片,硬盘,内存等组成。CPU芯片也使用类来表示。
CPU.h
#pragma once #include <string> class CPU { public: CPU(const char *brand = "intel", const char *version="i5"); ~CPU(); private: std::string brand; //品牌 std::string version; //型号 }; |
CPU.cpp
#include "CPU.h" #include <iostream> CPU::CPU(const char *brand, const char *version) { this->brand = brand; this->version = version; std::cout << __FUNCTION__ << std::endl; } CPU::~CPU() { std::cout << __FUNCTION__ << std::endl; } |
Computer.h
#pragma once #include "CPU.h" class Computer { public: Computer(const char *cpuBrand, const char *cpuVersion, int hardDisk, int memory); ~Computer(); private: CPU cpu; // Computer和CPU是“组合”关系 int hardDisk; //硬盘, 单位:G int memory; //内存, 单位:G }; |
Computer.cpp
#include "Computer.h" #include <iostream> Computer::Computer(const char *cpuBrand, const char *cpuVersion, int hardDisk, int memory):cpu(cpuBrand, cpuVersion) { this->hardDisk = hardDisk; this->memory = memory; std::cout << __FUNCTION__ << std::endl; } Computer::~Computer() { std::cout << __FUNCTION__ << std::endl; } |
Main.cpp
#include <iostream> #include <Windows.h> #include <string> #include <string.h> #include "Computer.h" using namespace std; void test() { Computer a("intel", "i9", 1000, 8); } int main(void) { test(); system("pause"); return 0; } |
小结:被拥有的对象(芯片)的生命周期与其拥有者(计算机)的生命周期是一致的。计算机被创建时,芯片也随之创建。计算机被销毁时,芯片也随之销毁。拥有者需要对被拥有者负责,是一种比较强的关系,是整体与部分的关系。
具体组合方式:1)被组合的对象直接使用成员对象。(常用)
2)使用指针表示被组合的对象,在构造函数中,创建被组合的对象;在析构函数中,释放被组合的对象。
UML中的组合表示:
注意包含者使用实心菱形。【补充】UML画图工具:starUML
聚合
需求:给计算机配一台音响。
Computer.h
#pragma once #include "CPU.h" class VoiceBox; class Computer { public: Computer(const char *cpuBrand, const char *cpuVersion, int hardDisk, int memory); ~Computer(); void addVoiceBox(VoiceBox *box); private: CPU cpu; // Computer和CPU是“组合”关系 int hardDisk; //硬盘, 单位:G int memory; //内存, 单位:G VoiceBox *box; //音箱 }; |
Computer.cpp
#include "Computer.h" #include <iostream> #include "VoiceBox.h" Computer::Computer(const char *cpuBrand, const char *cpuVersion, int hardDisk, int memory):cpu(cpuBrand, cpuVersion) { this->hardDisk = hardDisk; this->memory = memory; std::cout << __FUNCTION__ << std::endl; } void Computer::addVoiceBox(VoiceBox *box) { this->box = box; } Computer::~Computer() { std::cout << __FUNCTION__ << std::endl; } |
Main.cpp
#include <iostream> #include <Windows.h> #include <string> #include <string.h> #include "Computer.h" #include "VoiceBox.h" using namespace std; void test(VoiceBox *box) { Computer a("intel", "i9", 1000, 8); a.addVoiceBox(box); } int main(void) { VoiceBox box; test(&box); system("pause"); return 0; } |
聚合不是组成关系,被包含的对象,也可能被其他对象包含。拥有者,不需要对被拥有的对象的生命周期负责。
UML中的组合表示:
相关文章:
C++面向对象
面向对象的思想 面向过程: 根据程序的执行过程,来设计软件的所有细节。面向过程的缺点:开发大型项目时,越来越难以把控,甚至失去控制。后期维护、更新成本很大。解决方案:使用面向对象。 什么是面向对象…...
守护进程编程
目录 一、守护进程 1.1 守护进程概述 1.2 守护进程的功能及特点 1.2.1 守护进程的功能 1.2.2 守护进程的特点 1.3 主要过程 1.4 阿里云服务器编程实现守护进程 1.4.1 daemon 命令 1.4.2 nohup命令 1.4.3 fork()编程实现 1.5 在树莓派中通过三种方式创建守护进程 1.5…...
【Spring Boot 源码学习】深入 ConfigurableEnvironment 的初始化过程
《Spring Boot 源码学习系列》 深入 ConfigurableEnvironment 的初始化过程 一、引言二、配置环境的初始化2.1 源码总览2.2 prepareEnvironment 方法2.2.1 获取或创建可配置环境2.2.2 配置环境并设置参数2.2.3 将配置属性源附加到环境中2.2.4 触发环境准备事件2.2.5 将DefaultP…...
若依集成BladeX单点登录的令牌管理与api请求流程
目录 概述系统架构单点登录流程令牌管理机制接口调用流程关键代码实现数据结构安全性考虑常见问题与解决 概述 本文档详细说明若依系统如何实现与BladeX的单点登录集成,包括令牌管理和接口调用的完整流程。整个集成采用基于OAuth2的授权码流程,允许用…...
54常用控件_QLCDNumber的属性
目录 代码示例: 倒计时 QLCDNumer 是一个专门用来显示数字的控件.类似于“老式计算器”的效果 核心属性 属性 说明 intValue QLCDNumber显示的数字值(int). value QLCDNumber 显示的数字值(double). 和intValue是联动的. 例如给value设为1.5, intValue的值就是2. 另外&a…...
IcePlayer音乐播放器项目分析及学习指南
IcePlayer音乐播放器项目分析及学习指南 项目概述 IcePlayer是一个基于Qt5框架开发的音乐播放器应用程序,使用Visual Studio 2013作为开发环境。该项目实现了音乐播放、歌词显示、专辑图片获取等功能,展现了桌面应用程序开发的核心技术和设计思想。 技…...
【ELF2学习板】Ne10进行FFT测试
目录 引言 Ne10简介 交叉编译Ne10 测试 测试程序 测试结果 结语 引言 在上一篇博文介绍了FFTW在ELF2开发板的测试。其中我们提到--enable-neon选项在aarch64平台下无法启用。接下来测试一个专门用NEON指令优化的FFT库Ne10。 Ne10简介 NE10 是一个面向 ARM 架构的开源数…...
Android device PCO (protocol configuration options) intro
术语 英文缩写英文全称中文PCOprotocol configuration options协议配置选项RILradio interface layer 无线电接口层PCO介绍 PCO(Protocol Configuration Options) 是 3GPP 标准协议(TS 24.008)中定义的核心概念,用于在 LTE/5G 网络建立 PDN 连接时传递动态配置参数(如 D…...
HAL库通过FATFS和SDIO+DMA写入SD卡数据错误
HAL库F4版本 1.28.1 最近在使用HAL库配置SDIODMA并通过FATFS向SD卡写入数据,但是发现写入的数据经常有错误,不是少了一部分就是多了一部分,写入的数据为csv格式,通过循环向缓冲区写入"100100,12.345678\r\n"数据来观察问…...
RK Android11 修改默认语言为法语及时区为巴黎时间
文章目录 1、需求2、解决 1、需求 客户要求将系统默认语言改为法语,系统默认时区改为巴黎时间(也称为欧洲中部时间)2、解决 --- a/build/make/tools/buildinfo.shb/build/make/tools/buildinfo.sh-46,7 46,7 echo "ro.product.cpu.ab…...
文件上传Ⅰ
文件上传--前后端验证 不让上传php,所以要绕过它 遇到网站可能不是php语言,会是java或者python语言等,它只能解析网站本身的语言,那我们就上传符合网站语言识别的格式(它能解析什么后缀,就上传什么后缀)&…...
IntelliJ IDEA clean git password
IntelliJ IDEA clean git password 清除git密码 方法一:(这个要特别注意啊,恢复默认设置,你的插件什么要重新下载了) File->Manage IDE Settings->Restore Default Settings以恢复IDEA的默认设置(可选); 清空…...
【C++指南】哈希驱动的封装:如何让unordered_map/set飞得更快更稳?【上】
🌟 各位看官好,我是egoist2023! 🌍 种一棵树最好是十年前,其次是现在! 💬 注意:本文在哈希函数中主讲除法散列法,乘法散列法、全域散列法、双重散列等自行了解。 &#x…...
论坛测试报告
作者前言 🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂 🎂 作者介绍: 🎂🎂 🎂 🎉🎉🎉…...
人脸扫描黑科技:多相机人脸扫描设备,打造你的专属数字分身
随着科技的迅猛发展,人脸扫描这个词已经并不陌生,通过人脸扫描设备制作超写实人脸可以为影视制作打造逼真角色、提升游戏沉浸感,还能助力教育机构等领域生产数字人以丰富教学资源,还在安防、身份识别等领域发挥关键作用࿰…...
统计字符串每个字符出现频率
输入一个字符串,统计每个字符的出现频率,然后判断最大频率与最小频率的差值 cnt: 如果 cnt 是质数,则输出 "Lucky Word" 和 差值; 否则输出 "No Answer" 和 0。 #include <bits/stdc.h> u…...
SQL-子查询
SQL子查询是嵌套在另一个SQL查询中的SELECT语句,将内部查询的结果作为外部查询的条件或者数据源。 核心概念 子查询是一个完整的SELECT语句,可以嵌入到其他查询的where, from, SELECT, HAVING等子句中,用于动态生成条件或临时数据集。 例如…...
Python(21)Python日期时间完全指南:从基础到实战注意事项
目录 一、背景与核心价值二、核心模块详解与实战2.1 datetime模块四剑客2.2 时区处理黄金法则 三、企业级应用案例3.1 全球日志分析系统3.2 金融交易时间校验 四、六大核心注意事项1. 时区意识优先原则2. 时间戳的精度陷阱3. 夏令时转换三步骤4. 日期格式化安全指南5. 性…...
【医学影像 AI】早产儿视网膜病变国际分类(第三版)
【医学影像 AI】早产儿视网膜病变国际分类(第三版) 0. 论文简介0.1 基本信息0.2 摘要0.3 缩略语0.4 ICROP3的关键内容总结 1. 引言2. 血管化位置:区域(ZONE)3. 加重病变与前加重病变4. 急性病变分期(阶段1-…...
焊接机器人的设计
一、引言 随着制造业的发展,焊接工艺在各个领域得到广泛应用。焊接机器人具有焊接质量高、效率高、劳动强度低等优点,能够满足现代制造业对焊接生产的要求。设计一款性能优良的焊接机器人,对于提高焊接生产的自动化水平和产品质量具有重要意…...
SQL SERVER里面也可以插入存储过程,操作TCP,WEBSOCKET吗?数据发生改变时用于通知客户端
SQL SERVER里面也可以插入存储过程,操作TCP,WEBSOCKET吗?数据发生改变时用于通知客户端SQL存储过程入门🔥 - 人生得E不早朝 深度思考完成 用时28秒 好的,用户的问题是:SQL Server里面是否可以通过插入存储过程来操作TCP或WebSocket&…...
利用 DeepSeek-R1 模型和计算机视觉微调模型实现马铃薯叶片疾病识别和分类
一、需求背景 在农业现代化进程中,作物健康监测对于保障粮食安全和提高农业生产效率起着举足轻重的作用。马铃薯作为全球重要的粮食作物之一,其种植面积广泛,产量和质量直接关系到众多农户的生计以及食品供应链的稳定。准确、快速地鉴别马铃…...
文章记单词 | 第39篇(六级)
一,单词释义 themselves [əmˈselvz] pron. 他们自己;她们自己;它们自己;(用以加强语气)他们亲自,她们亲自,它们亲自;(反身代词) 指某人或某物本…...
深入理解C++中string的深浅拷贝
目录 一、引言 二、浅拷贝与深拷贝的基本概念 2.1 浅拷贝 2.2 深拷贝 在C 中, string 类的深浅拷贝有着重要的区别。 浅拷贝 深拷贝 string 类中的其他构造函数及操作 resize 构造 构造(赋值构造) 构造(拼接构造…...
C++ 常用的智能指针
C 智能指针 一、智能指针类型概览 C 标准库提供以下智能指针(需包含头文件 <memory>): unique_ptr:独占所有权,不可复制, 可移动shared_ptr:共享所有权,用于引用计数weak_pt…...
【AI部署】腾讯云GPU-常见故障—SadTalker的AI数字人视频—未来之窗超算中心 tb-lightly
ERROR: Could not find a version that satisfies the requirement tb-nightly (from torchreid) (from versions: none) ERROR: No matching distribution found for tb-nightly 解决 阿里云 python -m pip install tb-nightly -i https://mirrors.aliyun.com/pypi/simple …...
三大等待和三大切换
三大等待 1、三大等待:等待的方式有三种:强制等待,隐性等待,显性等待。 1、强制等待:time.sleep(2),秒 优点:使用简单缺点:等待时间把握不准,容易造成时间浪费或者等待时…...
工程化实践:Flutter项目结构与规范
工程化实践:Flutter项目结构与规范 在Flutter项目开发中,良好的工程化实践对于提高开发效率、保证代码质量和团队协作至关重要。本文将从项目结构、代码规范、CI/CD流程搭建以及包管理等方面,详细介绍Flutter项目的工程化最佳实践。 项目结…...
数据结构-Map和Set
文章目录 1. 搜索树2. Map3. Set4. 哈希表4.1 哈希表的基本概念4.2 哈希表的实现方法4.3 Java中的哈希表实现 5. 哈希桶哈希桶的实现方式哈希桶的作用哈希桶的应用模拟实现 1. 搜索树 二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树࿰…...
cpolar 内网穿透 实现公网可以访问本机
1、登录网站,升级成专业版,测试的话建议选一个月付费,选择预留 2、保留的TCP地址增加一条记录,描述可以自己取 3、验证,生成一个Authtocken码 4、在安装目录下,打开CMD命令,复制上面的码运行aut…...
QT调用ffmpeg库实现视频录制
可以通过QProcess调用ffmpeg命令行,也可以直接调用ffmpeg库,方便。 调用库 安装ffmpeg ffmpeg -version 没装就装 sudo apt-get update sudo apt-get install ffmpeg sudo apt-get install ffmpeg libavdevice-dev .pro引入库路径,引入库 LIBS += -L/usr/lib/aarch64-l…...
AI专题(一)----NLP2SQL探索以及解决方案
前面写了很多编码、算法、底层计算机原理等相关的技术专题,由于工作方向调整的缘故,今天开始切入AI人工智能相关介绍。本来按照规划,应该先从大模型的原理开始介绍会比较合适,但是计划赶不上变化,前面通用大模型的工作…...
Redis 的指令执行方式:Pipeline、事务与 Lua 脚本的对比
Pipeline 客户端将多条命令打包发送,服务器顺序执行并一次性返回所有结果。可以减少网络往返延迟(RTT)以提升吞吐量。 需要注意的是,Pipeline 中的命令按顺序执行,但中间可能被其他客户端的命令打断。 典型场景&…...
群辉默认docker数据存储路径
做一下笔记 今天不小心路径规划错误,好不容易找到了数据,特此做个路径记录。 /var/packages/ContainerManager/var/docker/...
【C++】入门基础【上】
目录 一、C的发展历史二、C学习书籍推荐三、C的第一个程序1、命名空间namespace2、命名空间的使用3、头文件<iostream>是干什么的? 个人主页<—请点击 C专栏<—请点击 一、C的发展历史 C的起源可以追溯到1979年,当时Bjarne Stroustrup(本…...
Git LFS 学习笔记:原理、配置、实践与心路历程
最近在学习 Git LFS,把一些零散的笔记整理成一篇博文,记录我的学习思路与心路历程。以下内容均为个人理解总结,部分尚未在生产项目中验证,仅供回顾与参考。 🔍 Git LFS 是什么?原理是什么? 刚接…...
SpringBoot集成oshi 查询系统数据
实现功能: <!-- 获取系统信息 --><dependency><groupId>com.github.oshi</groupId><artifactId>oshi-core</artifactId><version>6.6.1</version></dependency><dependency><groupI…...
iOS Facebook 登录
iOS Facebook 登录 官方文档 SDK下载链接...
uniapp打包IOS私钥证书过期了,如何在非mac系统操作
在非Mac系统下解决uniapp打包iOS私钥证书过期的问题,需通过以下步骤实现: --- ### **一、重新生成iOS证书(非Mac环境操作)** 1. **生成私钥和CSR文件** 使用OpenSSL工具(需提前安装)生成私钥和证书签…...
Axios的使用
Axios 是一个基于 Promise 的现代化 HTTP 客户端库,专为浏览器和 Node.js 设计。在企业级应用中,它凭借以下核心优势成为首选方案: 一、Axios 的核心优势 特性说明Promise 支持天然支持异步编程,避免回调地狱拦截器机制可全局拦截…...
第八篇:系统分析师第三遍——3、4章
目录 一、目标二、计划三、完成情况四、意外之喜(最少2点)1.计划内的明确认知和思想的提升标志2.计划外的具体事情提升内容和标志 五、总结 一、目标 通过参加考试,训练学习能力,而非单纯以拿证为目的。 1.在复习过程中,训练快速阅读能力、掌…...
【2025-泛计算机类-保研/考研经验帖征集】
【2025-泛计算机类-保研/考研经验帖征集】 打扰您1分钟时间看下这里: 这是一个无偿为爱发电的项目,旨在收集湖南大学2025届毕业的计算机类学科同学的经验帖, 我将定期汇总链接,在校内推免群中宣传,为校内的学弟学妹们…...
Flink介绍——实时计算核心论文之Kafka论文详解
引入 我们通过S4和Storm论文的以下文章,已经对S4和Storm有了不错的认识: S4论文详解S4论文总结Storm论文详解Storm论文总结 不过,在讲解这两篇论文的时候,我们其实没有去搞清楚对应的流式数据是从哪里来的。虽然S4里有Keyless …...
细节:如何制作高质量的VR全景图
细节:如何制作高质量的VR全景图 VR全景图是通过虚拟现实和3D技术实现的全景展示方式,能够将实景以1:1的比例等比复刻,并还原到互联网上,使用户能够在线上游览世界,获得沉浸式的体验。制作高质量的VR全景图是一个复杂而…...
深度学习中的概念——元素积(哈达玛积)
元素积操作(哈达玛积) 🔢 基本定义 矩阵的哈达玛积 对于两个同维度的矩阵: A [ a i j ] , B [ b i j ] A [a_{ij}], \quad B [b_{ij}] A[aij],B[bij] 它们的哈达玛积定义为: C A ∘ B 其中 c i j a i j…...
探索 Flowable 后端表达式:简化流程自动化
什么是后端表达式? 在 Flowable 中,后端表达式是一种强大的工具,用于在流程、案例或决策表执行期间动态获取或设置变量。它还能实现自定义逻辑,或将复杂逻辑委托…… 后端表达式在 Flowable 的后端运行,无法访问前端…...
AI语音助手 React 组件使用js-audio-recorder实现,将获取到的语音转成base64发送给后端,后端接口返回文本内容
页面效果: js代码: import React, { useState, useRef, useEffect } from react; import { Layout, List, Input, Button, Avatar, Space, Typography, message } from antd; import { SendOutlined, UserOutlined, RobotOutlined, AudioOutlined, Stop…...
《软件设计师》复习笔记(11.6)——系统转换、系统维护、系统评价
目录 一、遗留系统(Legacy System) 定义: 特点: 演化策略(基于价值与技术评估): 高水平 - 低价值: 高水平 - 高价值: 低水平 - 低价值: 低水平 - 高价…...
学习threejs,使用EffectComposer后期处理组合器(采用RenderPass、GlitchPass渲染通道)
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.EffectComposer 后期…...
Yarn的定义?
YARN(Yet Another Resource Negotiator) 是 Apache Hadoop 的核心组件之一,负责集群的资源管理和任务调度。它的主要作用是将 Hadoop 的资源管理和作业调度/监控功能分离,形成一个通用的资源管理平台,可以支持多种计算…...