
























开始面向对象
菜鸟教程:C++ 类 & 对象 ~ C++ 多态
访问数据成员
精简代码
#include <iostream>
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// // 成员函数声明
double get(void);
void set( double len, double bre, double hei );
};
// 成员函数定义
double Box::get(void)
{
return length * breadth * height;
}
void Box::set( double len, double bre, double hei)
{
length = len;
breadth = bre;
height = hei;
}
int main()
{
Box Box1; // 声明 Box1,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
double volume = 0.0; // 用于存储体积
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
volume = Box1.height * Box1.length * Box1.breadth;
cout << volume <<endl;
Box3.set(16.0, 8.0, 12.0);
volume = Box3.get();
cout << volume <<endl;
}
首先关于get()和set():
1、
Box::get()里的get不是固定的,可以叫gett等任意合法标识符,只要遵循命名规则;这里可以理解为一种虽然不是什么固定写法,可以随便起名字但为了方便导致大家都用get2、其他成员函数也可以这么弄,即类内声明、类外使用作用域解析运算符
::定义3、成员变量不能用这种方式,成员变量是在类内定义描述对象属性,而不是像成员函数那样类外通过
::定义。
成员函数定义
Box::get()虽然不在类Box里直接写函数体,但通过作用域解析运算符::表明它是类Box的成员函数,类里只是声明,函数体可在类外定义
Box1和Box3本质都是Box类的对象,只是初始化方式不同,Box1直接对成员变量赋值,Box3通过调用set成员函数来设置成员变量的值。
注意到Box类里先对get成员函数声明了, 然后外面进行了定义:
内部声明后,定义写在内外都可以
而如果内部不声明,定义只能写在内部,不可以写在外部
注意到没写private还是public:
get和set函数虽然没显式写public或private,但它们是公有的。原因如下:
- 类成员访问控制规则:在 C++ 的类定义中,若不指定访问控制修饰符,成员的访问权限由上一个指定的访问控制修饰符决定。你代码里
public:已经出现,后续未指定访问控制修饰符的成员函数(像get和set函数的声明)就都遵循此public权限,也就是这些函数能在类外被调用class ExampleClass { int privateVar; // 未指定,默认 private void privateFunc() {} // 未指定,默认 private public: int publicVar; int publichaha; //未指定,由上一个public来定 };由于类没显示指定访问权限,则默认是private的,再结合没指定也由上一个指定的访问权限规则,得知上述代码
—————————————————— C++ 继承 ——————————————————
东西好多,他妈的
首先关于: 和 ::
::叫作用域解析运算符:主要用于指定要访问的标识符(如变量、函数、类等)所在的作用域,几个场景:当在类外定义成员函数时,左侧是类名,右侧是成员函数名。
class MyClass { public: void myFunction(); }; void MyClass::myFunction() { // 函数体 }在使用命名空间中的标识符时,左侧是命名空间名,右侧是该命名空间内的标识符
namespace MyNamespace { int myVariable = 10; } int main() { int value = MyNamespace::myVariable; return 0; }
MyNamespace::myVariable表示访问MyNamespace命名空间里的myVariable变量使用
::前面不写内容,表示全局作用域。例如当局部变量和全局变量同名时,可通过这种方式访问全局变量#include <iostream> int globalVar = 20; int main() { int globalVar = 10; std::cout << "Local variable: " << globalVar << std::endl; std::cout << "Global variable: " << ::globalVar << std::endl; }class Animal { public: void makeSound() { std::cout << "Some generic animal sound." << std::endl; } }; class Dog : public Animal { public: void makeSound() { std::cout << "Woof!" << std::endl; } void callBaseSound() { // 调用基类的 makeSound 函数 Animal::makeSound(); } };而
:是继承// 基类 class Animal { // eat() 函数 // sleep() 函数 }; // 派生类 class Dog : public Animal { // bark() 函数 };
初学者可以简单理解为
::是从左到右一次缩小范围
:则是右边的是主(基类),派生是儿子(左边)
关于访问修饰符
菜鸟教程里的class Rectangle: public Shape继承了基类的全部成员函数和成员变量
问豆包更多:
一般情况下,若继承方式为公有(
public)、保护(protected)继承,派生类会包含基类的全部成员(包括成员变量和成员函数)但对于私有(
private)继承也包含基类全部成员,只是访问权限有变化。不过,构造函数、析构函数和赋值运算符重载函数不会被继承,而是会根据自身情况重新定义。
- 公有继承(
public):基类的公有成员和保护成员在派生类中的访问权限不变,私有成员在派生类中不可直接访问。- 保护继承(
protected):基类的公有成员和保护成员在派生类中变为保护成员,私有成员在派生类中不可直接访问。- 私有继承(
private):基类的公有成员和保护成员在派生类中变为私有成员,私有成员在派生类中不可直接访问。
但说了等于没说,看具体例子
#include <iostream>
class Base {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};
// 公有继承
class PublicDerived : public Base {
public:
void accessMembers() {
publicMember = 1; // 可访问
protectedMember = 2; // 可访问
// privateMember = 3; // 不可访问
}
};
// 保护继承
class ProtectedDerived : protected Base {
public:
void accessMembers() {
publicMember = 1; // 可访问
protectedMember = 2; // 可访问
// privateMember = 3; // 不可访问
}
};
// 私有继承
class PrivateDerived : private Base {
public:
void accessMembers() {
publicMember = 1; // 可访问
protectedMember = 2; // 可访问
// privateMember = 3; // 不可访问
}
};
int main() {
PublicDerived publicDerived;
publicDerived.publicMember = 1; // 可访问
ProtectedDerived protectedDerived;
// protectedDerived.publicMember = 1; // 不可访问,已变为保护成员
PrivateDerived privateDerived;
// privateDerived.publicMember = 1; // 不可访问,已变为私有成员
}
- 公有继承能在派生类里访问基类
protectedMember,在类外main不行,因protected成员只能类内或派生类用。- 保护继承在派生类的成员函数里可访问
publicMember和protectedMember,示例中accessMembers函数就能访问,在保护继承中,基类的publicMember和protectedMember在派生类中都变为了保护成员。而保护成员只能在类内或者其派生类内访问,在main函数(类外部)是无法直接访问的。不过,你可以在派生类中定义公有的访问函数来间接访问这些成员。#include <iostream> class Base { public: int publicMember; protected: int protectedMember; private: int privateMember; }; // 保护继承 class ProtectedDerived : protected Base { public: // 提供公有接口来访问基类的 publicMember void setPublicMember(int value) { publicMember = value; } int getPublicMember() { return publicMember; } // 提供公有接口来访问基类的 protectedMember void setProtectedMember(int value) { protectedMember = value; } int getProtectedMember() { return protectedMember; } }; int main() { ProtectedDerived protectedDerived; // 通过派生类提供的公有接口设置和获取成员值 protectedDerived.setPublicMember(10); std::cout << "publicMember: " << protectedDerived.getPublicMember() << std::endl; protectedDerived.setProtectedMember(20); std::cout << "protectedMember: " << protectedDerived.getProtectedMember() << std::endl; }- 私有继承同理,在派生类的成员函数里可访问
publicMember和protectedMember,像accessMembers函数。- 基类
privateMember只能在基类自身成员函数里用,派生类和类外都没法直接用。想在main里访问也要定义公有的访问函数来间接访问这些成员#include <iostream> class Base { public: int publicMember; protected: int protectedMember; }; // 私有继承 class PrivateDerived : private Base { public: // 定义公有接口来访问基类的 publicMember void setPublicMember(int val) { publicMember = val; } int getPublicMember() { return publicMember; } // 定义公有接口来访问基类的 protectedMember void setProtectedMember(int val) { protectedMember = val; } int getProtectedMember() { return protectedMember; } }; int main() { PrivateDerived privateObj; privateObj.setPublicMember(10); std::cout << "publicMember: " << privateObj.getPublicMember() << std::endl; privateObj.setProtectedMember(20); std::cout << "protectedMember: " << privateObj.getProtectedMember() << std::endl; }
这种基本问题,没啥深度难度的问豆包就可以了
总结:
1、基类构造函数不继承,派生类一般要在构造函数里调用基类构造函数,可按需决定是否额外写逻辑;
2、公有继承可以在基类内和继承类内访问基类的公有和保护的成员变量和函数,私有成员变量和函数无法直接在派生类内和main内访问,想访问要弄个公有函数,而main里可以访问公有函数和变量,想访问保护类型变量和函数也要弄个共有函数 且公有继承,main 里能访问基类公有成员;保护和私有继承,若不写公有函数,main 里无法访问基类原公有和保护成员 。 保护继承在派生类写公有访问,然后可以在基类和派生类内访问公有和保护成员函数和变量,而main里只能访问公有成员变量和函数,对于保护和私有成员变量和函数想在main和派生类里访问都需要弄个函数 私有继承在派生类内要写个公有函数来访问基类里的公有和保护变量和函数,而访问基类里的私有变量和函数或者mian里访问都要弄个公有函数来访问公有、保护、私有成员变量和函数都要写个公有访问函数
3、
Q:
代码中派生类里为何还要写个public?然后void accessMembers?
A:
是因为外界(如
main函数)就能调用它,若不写public的accessMember函数,main里无法访问派生类非公有的、保护和私有类型成员。4、
Q:
那我看派生类里写了个public然后直接可以用基类里的相应函数和变量了,为何我看你又写了一种写法是get和set呢?
A:
写
get和set函数,是为了更好地封装和控制对成员变量的访问。直接用基类函数和变量可能会破坏封装性,而get和set能在访问时添加额外逻辑,如边界检查、数据验证等,增强代码的安全性和可维护性。
自己思考的很深,总结完得到豆包肯定后再回头看菜鸟教程里的“我们可以根据访问权限总结出不同的访问类型,如下所示”那个表就太小儿科了
自己问完豆包,再回来看多继承也明白了,感觉也没啥难的,就是太绕了,真他妈恶心
问了下豆包,找工作的东西,╮(╯▽╰)╭~~~~(>_<)~~~~ 之前的咨询
—————————————————— C++ 重载运算符和重载函数 ——————————————————
C++ 重载运算符和重载函数
重载函数
重载运算符
#include <iostream>
// 定义 Box 类
class Box {
private:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
public:
// 构造函数,用于初始化盒子的尺寸
Box(double l = 0, double b = 0, double h = 0) : length(l), breadth(b), height(h) {}
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b) {
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
// 成员函数,用于计算盒子的体积
double getVolume() {
return length * breadth * height;
}
};
int main() {
// 创建两个 Box 对象
Box box1(1.0, 2.0, 3.0);
Box box2(4.0, 5.0, 6.0);
// 使用重载的 + 运算符将两个 Box 对象相加
Box box3 = box1 + box2;
// 输出每个盒子的体积
std::cout << "Box 1 Volume: " << box1.getVolume() << std::endl;
std::cout << "Box 2 Volume: " << box2.getVolume() << std::endl;
std::cout << "Box 3 (box1 + box2) Volume: " << box3.getVolume() << std::endl;
}
关于未定义,我理解最终没初始化就是未定义:
不完全是。局部作用域的内置类型(如
int、double)未初始化,其值是未定义的,使用会导致未预期结果;但全局或静态存储期的内置类型变量未显式初始化会被零初始化;类类型对象未显式初始化会调用默认构造函数,若默认构造函数完成初始化则并非未定义。
关于构造函数传参问题,上一个博客,说“对于这里的构造函数,可以不写”,但实际上虽然
#include <iostream>
#include <string>
using namespace std;
struct Books{
string title;
int book_id;
// Books(string t, int id)
// : title(t), book_id(id) {}
};
void printBookInfo(const Books& book) {
cout << "书籍标题: " << book.title << endl;
cout << "书籍 ID: " << book.book_id << endl;
}
int main(){
Books Book1("C++ 教程",12345);
Book1.title="C++ 教程";
printBookInfo(Book1);
}
通过编译了,但说是可能版本还是编译器这些我最讨厌的问题,导致的
严格来说
没构造函数时,不能传参初始化对象
有带参数的构造函数,必须传参初始化对象,除非同时定义了无参数的构造函数
C++ 会根据操作数的类型来决定使用哪个 + 运算符:如果操作数是内置类型,就使用内置的加法规则;如果操作数是自定义类型且该类型重载了 + 运算符,就调用重载的运算符函数。
操作数是参与运算的对象,在 Box box3 = box1 + box2; 里 box1 和 box2 是操作数,在 int result = a + b; 中 a 和 b 是操作数。
当把加法运算符重载函数定义为非成员函数时,由于它不在类内部,没有隐含的当前对象,所以需要显式传入两个操作数,因此函数原型为 Box operator+(const Box&, const Box&); ,其中两个 const Box& 分别表示两个参与加法运算的 Box 对象的引用。
现在说下类成员函数
我一直因为是什么高大上的玩意,结果tmd根本不是类内函数本身有什么参数,而是main里传入的实参那,其实相当于省略了一个写法,无语
在 C++ 中,当调用类的成员函数时,编译器会自动将调用该函数的对象的地址作为一个隐含的参数传递给函数,这个隐含的参数就是
this指针。对于运算符重载函数也是如此。当在
main函数中执行box1 + box2时,实际上等价于调用box1.operator+(box2)。这里box1的地址通过this指针隐式传递给operator+函数,而box2作为显式参数传递。
运算符重载实例
看到个有意思的
函数调用运算符 () 可以被重载用于类的对象。当重载 () 时,您不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。
safearay()与Box(double l = 0, double b = 0, double h = 0) : length(l), breadth(b), height(h) {}都是构造函数,只是前者无参,在函数体中初始化数组,后者有默认参数,用成员初始化列表初始化成员变量。
safearay()是safearay类的构造函数。在 C++ 中,构造函数是一种特殊的成员函数,其名称与类名相同,并且没有返回类型(包括void)。构造函数的主要作用是在创建类的对象时对对象进行初始化操作。在
safearay类的构造函数中,它会遍历数组arr的每个元素,并将元素的下标值赋给该元素。具体代码如下:safearay() { int i; for(i = 0; i < SIZE; i++) { arr[i] = i; } }当你在
main函数中创建safearay类的对象A时,safearay()构造函数会被自动调用,从而将数组arr的元素初始化为{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}。
int& operator[](int i)是对下标运算符[]的重载。在 C++ 中,运算符重载允许你为自定义类型(如类)重新定义运算符的行为。具体解释如下:
operator[]:这是 C++ 中用于重载下标运算符[]的固定语法。当你使用对象名[索引]的形式时,实际上会调用这个重载的operator[]函数。int&:返回值类型为int&,表示返回一个int类型的引用。引用的作用是允许你直接修改数组中的元素。如果返回的是int而不是int&,那么返回的只是数组元素的一个副本,而不是元素本身,这样就无法通过返回值修改数组元素。(int i):这是函数的参数列表,i表示要访问的数组元素的下标。例如,在
main函数中使用A[2]时,实际上会调用A.operator[](2),该函数会返回数组arr中索引为 2 的元素的引用,从而可以直接访问和修改该元素。虽然使用普通的数组
int n[10]可以很方便地定义和使用数组,但重载下标运算符在某些情况下具有重要的优势:
- 边界检查:在实际应用中,普通数组不会进行边界检查,如果你访问的下标超出了数组的有效范围,会导致未定义行为,可能会引发程序崩溃或产生不可预测的结果。而通过重载下标运算符,你可以在
operator[]函数中添加边界检查逻辑,确保访问的下标在有效范围内,从而提高程序的健壮性。例如:int& operator[](int i) { if (i < 0 || i >= SIZE) { // 处理越界情况,如抛出异常 throw std::out_of_range("Index out of range"); } return arr[i]; }
封装性:使用类来封装数组可以隐藏数组的具体实现细节,只提供必要的接口给外部使用。这样可以避免外部代码直接访问和修改数组,提高代码的安全性和可维护性。
自定义行为:重载下标运算符可以实现一些自定义的行为,例如对数组元素进行特殊的处理或转换。你可以根据具体需求在
operator[]函数中添加额外的逻辑,而普通数组无法做到这一点。A这个对象内部封装了一个数组。
纯傻逼,先略过
———————————————————— C++ 多态 ————————————————————
先理清楚几个知识点(都是问豆包的)
1、继承机制产生了父子类的概念,
基类叫父(被继承的类通常被叫做基类或者父类)
派生类叫子(通过继承基类产生的新类被称作派生类或者子类)
2、当我们用子类去创建一个变量时,这个变量就代表了一个子类对象。
3、多态比继成功牛逼在哪啊?
#include <iostream> // 父类 Animal class Animal { public: void call() { std::cout << "动物发出声音" << std::endl; } }; // 子类 Cat class Cat : public Animal { public: void call() { std::cout << "喵喵喵" << std::endl; } }; // 子类 Dog class Dog : public Animal { public: void call() { std::cout << "汪汪汪" << std::endl; } }; int main() { Cat cat; Dog dog; cat.call(); dog.call(); }要是修改
Cat类的call方法,由于调用是直接针对Cat对象,所有调用cat.call()的地方都要确认是否会受到影响。如果项目里有很多地方调用了cat.call(),修改时就很容易出错,维护成本高。Q:不是吧,我想修改成“喵呜喵呜” ,直接把派生类输出换成 ”喵呜喵呜” 就好了啊,咋需要改那么多东西?
A:
你说得对,单纯修改派生类(子类)里的输出内容时,不管是多态还是普通继承,确实都只需要在子类的对应方法里改。但实际开发中,情况会更复杂,下面给你详细说说多态在增强可维护性上的优势。
假设除了输出叫声,
call方法还会调用其他辅助方法或者有一些额外逻辑,并且在不同调用处有不同的参数传递和后续操作。普通继承场景
#include <iostream> class Animal { public: void commonFunction() { std::cout << "执行一些通用操作" << std::endl; } }; class Cat : public Animal { public: void call() { commonFunction(); std::cout << "喵喵喵" << std::endl; } }; void specificCall(Cat cat) { cat.call(); // 这里有针对 Cat 对象 call 之后的特殊操作 std::cout << "执行针对猫叫后的特殊操作" << std::endl; } int main() { Cat cat; specificCall(cat); }如果要修改
Cat类的call方法,比如添加一些额外逻辑或者修改辅助方法的调用方式,由于specificCall函数是专门针对Cat对象编写的,可能需要检查并修改specificCall函数以及其他所有调用Cat对象call方法的地方,以确保修改不会引发错误多态场景
#include <iostream> class Animal { public: virtual void call() { std::cout << "执行一些通用操作" << std::endl; } }; class Cat : public Animal { public: void call() override { Animal::call(); std::cout << "喵喵喵" << std::endl; } }; void makeAnimalCall(Animal* animal) { animal->call(); // 这里的操作不依赖具体是哪种动物 std::cout << "执行通用的动物叫声后续操作" << std::endl; } int main() { Cat cat; makeAnimalCall(&cat); }在多态的情况下,
makeAnimalCall函数接受的是Animal类型的指针。当修改Cat类的call方法时,makeAnimalCall函数不需要做任何修改,因为它只依赖于Animal类的接口,只要Animal类的接口不变,调用处就不受影响。这大大减少了修改代码时的影响范围,增强了代码的可维护性。所以,虽然简单的输出修改在两种方式下看起来区别不大,但在实际复杂的项目中,多态能更好地隔离修改带来的影响,让代码更易于维护。
继续问豆包:
Q:关于你说的“牛逼四:统一处理逻辑”举例子
A:
#include <iostream> // 父类 Animal class Animal { public: virtual void call() { std::cout << "动物发出声音" << std::endl; } }; // 子类 Cat class Cat : public Animal { public: void call() override { std::cout << "喵喵喵" << std::endl; } }; // 子类 Dog class Dog : public Animal { public: void call() override { std::cout << "汪汪汪" << std::endl; } }; int main() { Animal* animals[2]; Cat cat; Dog dog; animals[0] = &cat; animals[1] = &dog; for (int i = 0; i < 2; ++i) { animals[i]->call(); } return 0; }这里可以把
Cat和Dog对象的指针存到Animal类型的数组里,然后通过循环统一调用call方法,不管有多少种动物子类,都可以用这种简洁统一的方式处理。单纯继承情况
#include <iostream> // 父类 Animal class Animal { public: void call() { std::cout << "动物发出声音" << std::endl; } }; // 子类 Cat class Cat : public Animal { public: void call() { std::cout << "喵喵喵" << std::endl; } }; // 子类 Dog class Dog : public Animal { public: void call() { std::cout << "汪汪汪" << std::endl; } }; int main() { Cat cat; Dog dog; if (/* 判断是否是 Cat 对象 */) { cat.call(); } if (/* 判断是否是 Dog 对象 */) { dog.call(); } return 0; }当需要处理多个子类对象时,只能通过条件判断区分不同子类对象,然后分别调用对应方法。随着子类数量增多,条件判断会越来越复杂,代码可读性和可维护性变差,而多态避免了这些问题,让代码更简洁。
以上是我起手看菜鸟教程发现说跟继承有关系,故追问了许多
现在回头继续看菜鸟教程,直接把里面的实例1丢给豆包,又追问好久,发现懂了好多
不再记录了,太浪费时间了
不再强迫症了,以防失效,之前的 收获与成长

实例2,问完豆包后觉得太简单了,不过菜鸟教程里解释的也非常棒
以防失效

###:

妈逼的情人节0214图书馆坐位旁边一个身材一级棒,该肥的地方肥,该瘦的瘦,大奶子这tm又在磨练我心性好想一口下去
咳嗽一声太他妈销魂了,离得我还很近
可是经历使我立马冷静下来,做一个够狠够绝杀伐果断的人
女人只会磨灭我的斗志
一看是个考公的,呵呵,呵呵呵
View Code
###:学英文
class derived-class: access-specifier base-class
衍生 接近-说明符
即: 派生 访问说明符
polymorphism:多态性
###:豆包:
一句话回答我以下所有问题,且都是C++的,且每个问题换行,且如果需要写代码一律去掉最后的return 0
###:傻逼VS滚动代码跟他妈个僵尸一样,这么垃圾的残次品也这么受欢迎吗
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。