每日一言
所有的逆袭,都是有备而来;所有的幸运,都是努力埋下的伏笔。
真正的成员变量
为什么要说这个呢,其实实际上我们之前的
void (*printinfo)(string address,string name,string date);
这个是c语言这个面向过程的语言写面向对象的时候,用的函数指针来模拟的,函数的实现,但是在C++里面,这个不算是成员函数,只能算是成员函数指针变量(也是成员变量),所以我们写出真正的成员函数,
因为成员函数为什么叫做成员函数呢,因为在类的里面进行声明,而且不需要进行传参数,我们函数的实现需要放在外面,毕竟类是模板嘛,肯定不能写实现体啊,当然我们在外面写函数的实现的的时候需要加上void toy::
“::”这个双冒号是什么意思呢??
双冒号 :: 称为 "作用域解析运算符"(Scope Resolution Operator)。它用于指定一
个成员(如函数或变量)属于特定的类或命名空间。例如,在类的外部定义成员函数时, :: 用于
指明该函数属于哪个类。
所以啊我们需要指明这个函数属于哪个类的,像我们这个reallyToyinfo是在toy类里面声明的,所以是属于toy类的,因此在类的外部写实现体的时候需要在前面加上void toy:: void是函数的返回值类型和toy里面的类型相对应
这个是我们如何调用成员函数
类的组合:
有时候我们看到QT项目的时候会看到
类似这样的写法
BMW.t2->brand="米其林";
BMW.t1.year="2033年8月2日";
Audi->t2->brand="倍耐力";
就很慌,那么我们现在了解一下这个是什么???
这个就叫做类的组合了,什么叫做类的组合?
主要是类里面包含了另一个类的对象,像这样
那么为什么要这样做,这个就是做嵌套一个感觉,就是比如车有品牌和出厂年份,那么车的轮胎是不是也有品牌和年份还有尺寸等等,那么我们可以,通过类的组合将这辆车的轮胎的信息就直接融合到这个车这边,是整体和部分的关系
那么我们就需要将轮胎这个类也要定义出来,因为我们要做类的组合嘛,看一下类的组合怎么发挥神力
我们来看整体代码
首先我们先看到这边
会想到为什么用这个cout输出为什么可以将address里面的字符全部输出出来,以及这个字符串拼接str的为什么可以通过这个cout将所有字符输出出来都是一个道理
涨知识:
细节:
address 和 date 是字符数组(C 风格字符串),而 cout 能够正确输出它们的内容,这涉及到 C++ 对字符数组的特殊处理机制。让我详细解释:
一、C 风格字符串(char[])的输出原理
当你执行 cout << address 时:
字符数组自动转换为指针
address 是 char[20] 类型的数组,但在表达式中会自动转换为指向首元素的指针(char*)。这是 C++ 的隐式类型转换规则。
cout 对 char* 的特殊处理
std::cout 重载了针对 char* 类型的 << 运算符:
std::ostream& operator<<(std::ostream& os, const char* str);
这个重载函数会从指针指向的地址开始,逐个输出字符,直到遇到 '\0' 终止符。因此,只要你的字符数组以 '\0' 结尾,cout 就能正确输出整个字符串。
- 当 cout 遇到字符数组名 address 时,会将其视为 C 风格字符串,从首地址开始逐个输出字符,直到遇到 '\0' 为止。
- 因此,address 数组中存储的所有有效字符(如 "中国")会被完整输出。
一、C++ 风格字符串(std : : string)的输出原理
std::string 类封装了一个动态字符数组,并维护了字符串的长度信息。它不依赖 '\0' 作为终止符(尽管在某些操作中可能会临时生成以 '\0' 结尾的 C 风格字符串)。
operator<< 的重载
C++ 标准库为 std::string 专门重载了 << 运算符:
std::ostream& operator<<(std::ostream& os, const std::string& str);
这个重载函数会:
-
- 直接访问 str 内部存储的字符数组
- 根据 str.size() 获取字符串长度
- cout会输出所有有效字符(从索引 0 到 size()-1),无需依赖 '\0'
切记的一点
你说得没错,std::string 内部通过指针管理字符数组,无论是拼接、遍历还是输出,本质上都是基于指针操作。但与 C 风格字符串不同的是:
- std::string 自动管理内存(分配与释放)
- 依赖 size_ 而非 '\0' 来确定字符串长度
- 提供更安全、便捷的接口(如 + 运算符、substr() 等)
这种设计让 std::string 在保持高效的同时,避免了 C 风格字符串的常见陷阱(如缓冲区溢出、忘记添加 '\0')。
回到讲解代码的地方:
这个就是最核心的代码了,因为我们知道
我们在类中调用了一个一个类的对象,那么我这里要提醒一下:
在 C++ 中,类的成员可以是:
- 基本数据类型(如int、string)
- 其他类的对象(如Tire t1)
- 指向其他类对象的指针(如Tire *t2)
- 函数或函数指针(如void (*printinfo)(...))
对的所以此时这个类中的Tire t1实际上也是car类的一个成员了,所以我们如果对他进行赋值的话,就需要通过car类进行赋值
比如
就是这个类的实例化对象,首先看我们这个大类的对象是普通对象还是指针对象,因为指针对象的调用方式是’ -> ’ , 普通对象是’ . ’ ,那么我们知道这一点之后就可以对这个小类的对象进行调用,那么再调用之前也要看这个小类的对象是指针对象还是普通对象,取决于这个小类对象该使用怎么样的调用方式对他自身类数据的赋值
拿这个来说 BMW.t1.year 说明BMW是普通对象、t1是普通对象
拿这个来说 BMW.t2->year 说明BMW是普通对象、t2是指针对象
拿这个来说 Audi->t2->brand 说明Audi是指针对象、t2是指针对象
然后最后一点就是我们的这个我们创建指针对象的时候,你就需要对这个指针对象进行开辟空间,那怎么开辟呢
比如我们这边t2 是car类的指针对象,那么我们car类的BMW对象对t2进行开辟空间的时候 就需要BWM.t2 =new Tire();
new Tire(); 这个代表是Tire类的对象,需要赋予的内存就是Tire那么大的内存
BWM.t2 代表是BMW这个对象的t2
综合讲就是对BMW的t2进行开辟一个Tire类的空间
- new Tire() 在堆内存中创建一个 Tire 对象,大小为 sizeof(Tire)。
- 返回该对象的地址,并将地址赋值给 BMW.t2。
涨知识:
一、核心结论:Tire* t2 是 “空壳子”,new Tire() 是 “填充实物”
t2 是指针变量
-
- 它本身只是一个存储地址的 “盒子”(通常 4/8 字节)。
- 初始时盒子是空的(值为随机值或 nullptr),不指向任何有效对象。
new Tire() 的作用
-
- 在堆内存中创建一个真实的 Tire 对象(包含 brand、year 等成员)。
- 返回这个对象的地址,把地址填入 t2 盒子中。
赋值后
-
- t2 不再是 “空壳子”,而是指向一个真实存在的 Tire 对象。
- 通过 t2->brand 就能访问这个对象的成员。
类的组合就到这里讲完了