C++初学者指南第一步---13.聚合类型

C++初学者指南第一步---13.聚合类型

文章目录

1. 类型分类(简化)

基本类型 void, bool, char, int, double, ... void、bool、char、int、double、...
简单聚合 主要用途:对数据进行分组 聚合:可能包含一个/多个基本类型或其他兼容的聚合类型 无法控制组成类型的相互作用 简单,(编译器生成的)默认构造/析构/复制/赋值。 标准的内存布局(所有成员按照声明顺序连续布置),如果所有成员具有相同的访问控制(例如全部为public)。
更复杂的自定义类型 自定义不变量和对成员相互作用的控制 限制成员访问 具有成员函数 用户自定义构造/成员初始化 用户自定义的析构 / 复制 / 赋值 可以是多态的(包含虚成员函数)

2. 如何定义和使用

示例:具有 2 个整数坐标的类型

代码段1

cpp 复制代码
struct point {
  int x;  // ← "成员变量"
  int y; 
};
// 创建新对象 (在栈中)
point p {44, 55}; 
// 打印成员变量的值
cout << p.x <<' '<< p.y;  // 44 55

分配给成员变量值:(代码段2)

cpp 复制代码
p.x = 10;
p.y = 20;
cout << p.x <<' '<< p.y;  // 10 20

成员变量的存储顺序与声明顺序相同。

代码段1的内存:

代码段2的内存:

运行上面代码

3. 为什么选择自定义类型/数据聚合?

接口变得更易于正确使用

  • 语义数据分组: 点,日期...
  • 避免了许多函数参数,从而减少了混乱。
  • 函数可以使用一个专门的类型来返回多个值,而不是使用多个非常量引用输出参数。

不使用则会有糟糕的接口:

void closest_point_on_line (double lx2, double ly1, double lx2i, double ly2, double px, double py, double& cpx, double& cpy) { ... }
  • 许多相同类型的参数⇒容易编写错误
  • 非常量引用输出参数 ⇒ 容易出错
  • 线的内部表示也被嵌入到接口中

使用则好多了:

struct point { double x; double y; };
struct line  { point a; point b; };
point closest_point_on_line (line const& l, point const& p) { ... }
  • 直接明了的接口
  • 易于正确使用
  • 如果线的内部表示更改了(例如,点+方向而不是2个点)⇒ 需要改变closest_point_on_line的实现方式,但其接口可以保持不变⇒ 大多数调用代码无需更改!

4. 聚合类型初始化

语法:

Type { arg1, arg2, ..., argN }
  • 花括号括起来的成员值列表

  • 按成员声明顺序排列

    enum class month {jan = 1, feb = 2,..., dec = 12};
    struct date {
    int yyyy;
    month mm;
    int dd;
    };
    int main () {
    date today {2020, month::mar, 15};
    // C++98的写法,依然正确:
    date tomorrow = {2020, month::mar, 16};
    }

5.混合

示例:作为 person 成员的日期

cpp 复制代码
enum class month { jan=1, feb=2,... , dec=12 };
struct date {   
  int yyyy;
  month mm;
  int dd;
};
struct person {
  std::string name;
  date bday;
};
int main () {
  person jlp { "Jean-Luc Picard", {2305, month::jul, 13} };
  cout << jlp.name;     // Jean-Luc Picard
  cout << jlp.bday.dd;  // 13
  date yesterday { 2020, month::jun, 16 };
  person rv = { "Ronald Villiers", yesterday };
}

运行上面代码

6. 复制

副本总是所有成员的深层拷贝!

cpp 复制代码
enum class month {jan = 1, ... };
struct date { 
  int yyyy;  month mm;  int dd; 
};
int main () {
  date a {2020, month::mar, 7};     
  date b = a;  // deep copy of a
  b.dd = 22;   // change b
}

main 函数最后一行后的状态:

拷贝构造 = 创建一个具有与源相同值的新对象
拷贝赋值 = 使用源对象的值覆盖现有对象的值

struct point { int x; int y; };
point p1 {1, 2};  // 构造
point p2 = p1;    // 拷贝构造
point p3 ( p1 );  // 拷贝构造
point p4 { p1 };  // 拷贝构造
auto  p5 = p1;    // 拷贝构造
auto  p6 ( p1 );  // 拷贝构造
auto  p7 { p1 };  // 拷贝构造
p3 = p2;  // 拷贝赋值
          // (p2和p3之前都存在)

7. 值和引用的语义

值语义

= 变量是指对象本身:

  • 深拷贝:生成一个新的、独立的对象;对象(成员)的值被复制。
  • 深度赋值:使目标的值等于源对象的值。
  • 深度所有权:成员变量引用与包含对象生命周期相同的对象。
  • 基于值的比较:如果它们的值相等,则变量相等。

值语义是几乎所有编程语言中基本类型(int、double等)的默认行为,也是C++中聚合类型/用户自定义类型的默认行为。

引用语义

= 变量是对对象的引用:

  • 浅层复制:变量的副本引用同一对象。
  • 浅层赋值:赋值使变量引用不同的对象。
  • 浅层所有权:成员变量也只是引用。
  • 基于身份的比较:如果变量引用同一对象,则比较相等。

大多数其他主流语言(Java、Python、C#、Swift 等) 对用户自定义类型使用(内置)引用语义。
C++ 的情况是一致的,能够提供全面的控制:

  • 默认情况下:所有类型都采用值语义(除了C风格数组)。
  • 所有类型都可以使用可选的引用语义(通过引用或指针)来实现。

8.聚合的向量(std::vector)

值语义 ⇒

  • vector 的存储包含类型 T 的对象本身,而不仅仅是对它们的引用或指针(就像在 Java/C#/... 中一样)。
  • 如果向量对象被释放 ⇒ 包含的 T 对象也会被释放。
vector v { 0,1,2,3,4 };
struct p2d { int x; int y; }; vector v {{1,2},{5,6},{8,9}};

9.最令人烦恼的解析

无法使用空括号进行对象构造,因为在C++语法中存在歧义。

struct A { ... };
A a();  // 声明了一个函数 'a'
        // 没有参数
        // 返回类型为 'A'
A a;    // 构造了一个 A 类型的对象
A a{}  // 也构造了一个 A 类型的对象

附上原文地址

如果文章对您有用,请随手点个赞,谢谢!^_^

相关推荐
魔道不误砍柴功1 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨4 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程32 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
UestcXiye1 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
Chrikk1 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
霁月风3 小时前
设计模式——适配器模式
c++·适配器模式
萧鼎3 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步