C语言中的构造数据类型如结构、联合、枚举等在C++中仍然有效。由于C++新增了一种类型名class,许多人错误地认为struct只能用来包装数据,或者class必须定义成员函数。
C++对C的结构、联合、枚举等进行了必要的改造和增强,本章比较分析了异同点,总结了使用要点,对于那些正在从C语言向C++语言过渡的程序员有较好的参考价值。
如果只能使用基本数据类型来编程,那将是一件痛苦的事情。C语言支持把基本数据类型组合起来形成更大的构造数据类型,这就是C语言的struct,有时也称为用户自定义数据类型(User Defined Type, UDT)。构造数据类型还可以嵌套(对象嵌入)和引用(对象关联),实际上,构造数据类型是一个递归的定义:
(1)由若干基本数据类型组合而成的类型是构造数据类型。
(2)由若干基本数据类型和构造数据类型组合而成的数据类型是构造数据类型。
(3)由若干构造数据类型组合而成的数据类型是构造数据类型。
语言本身的这种能力使我们能够定义非常复杂的数据结构,如树(tree)、链表(list)和映射(map)等。
C++语言对C语言的struct进行了改造,使其也可以像class那样支持成员函数的声明和定义,从而使struct变成真正的抽象数据类型(Abstract Data Type, ADT),这使得许多人对struct和class倍感困惑。
当语言支持某种特征时,是否使用这种特征则完全取决于程序员。因此,并不是说class支持成员函数的定义,我们就一定要在每一个class中都定义成员函数;也并不是说struct过去不支持成员函数定义,我们就非得用class完全取代struct。实际上就C++语言本身来讲,struct和class除了“默认的成员访问权限”这一点不同外,没有任何区别。
【提示8-1】:
|
在C++语言中,如果不特别指明,struct成员的默认访问限定符为public,而class成员的默认访问限定符为private。
|
因此,在C++程序中,只要你明确地声明每一个成员的访问权限,那么完全可以用struct取代class,也完全可以用class取代struct,见示例8-1。
示例8-1
struct SA
{
public :
const char * GetName( ) const;
private :
char *m_name ;
int m_height ;
int m_weight ;
};
|
class CA
{
public :
const char * GetName( ) const;
private :
char *m_name ;
int m_height ;
int m_weight ;
};
|
本例中SA和CA这两个类型在C++中没有任何不同。就像Lippman所说的那样,“在C++中,选择使用关键字struct还是class来定义UDT或ADT完全是一种观念上的差异,而关键字本身并没有代表这种差异”。
我们再看一看C++鼻祖Bjarne Stroustrup是如何说的:“带类的C和C语言几乎是‘代码兼容’的,并且也是连接兼容的。C的函数可以在带类的C程序中调用,带类的C函数也可以在C程序中调用;带类的C程序中的struct和C中的struct在两个语言里的布局都一致,所以可以在两个语言之间传递简单对象或组合对象。这种连接兼容性一直保持到C++中。”
C++仍然支持C风格的struct,并且还做了增强,主要是为了兼容遗留的C代码以使它们可以在新的C++环境下重新编译而继续“发挥余热”,可以让“过程式和结构化思想根深蒂固”的C程序员比较容易地过渡到面向对象的C++语言。关于这个问题更具哲学性的讨论请参考Lippman所著的《Inside The C++ Object Model》一书。
【建议8-1】:
|
为了不使程序产生混乱和妨碍理解,建议还使用struct定义简单的数据集合;而定义一些具有行为的ADT时最好采用class,如果采用struct似乎感觉不到面向对象的味道了。
|
使用struct
在C++环境中,我们把C风格的struct叫做POD(Plain Old Data)对象,从字面上你也可以知道它仅包含一些数据成员,这些数据成员可以是基本数据类型变量、任何类型的指针或引用、任何类型的数组及其他构造类型的对象等,见示例8-2。
示例8-2
【提示8-2】:
|
虽然把数组当作参数传递给函数的时候,数组将自动转换为指针,但是包装在struct/class中的数组,其内存空间则完全属于该struct/class的对象所有。如果把struct/class当作参数传递给函数时,默认为值传递,其中的数组将全部复制到函数堆栈中。例如:
void func (Student s)
{
cout << sizeof (s) << endl ; // 56
}
Student s0 ;
func (s0) ;
因此,当你的UDT/ADT中包含数组成员的时候,最好使用指针或引用传递该类型的对象,并且一定要防止数组元素越界,否则它会覆盖后面的结构成员。
|
任何POD对象的初始化都可以使用memset()函数或者其他类似的内存初始化函数。假设s是Student的一个对象,用memset()初始化s的方法如下:
memset (&s, 0x00, sizeof (Student)) ;
C风格的构造类型对象也可以在定义的时候指定初始值。我们可以仅指定第一个成员的初值来初始化POD对象,后面的成员将全部自动初始化为0,就像数组的初始化一样。例如:
Student s = { 0 };
结构可以嵌套定义,也就是在一个结构的定义体内定义另一个结构,见示例8-3。
示例8-3
【提示8-3】:
|
构造类型虽然可以嵌套定义,但是对于嵌套定义的类型,其对象不一定存在包含关系,存在包含关系的对象类型也不一定是嵌套定义的。例如,上例中的_Name类型完全可以挪到Student定义的外面某处,而它们的对象之间的包含关系不会改变。当一个类型A只会在另一个类型B中被使用的时候,就可以把A定义在B的定义体内,这样可以减少暴露在外面的用户自定义类型的个数。
|
所谓对象之间的包含是指一个类型的对象充当了另一个类型定义的数据成员,从而也就充当了它的对象的成员,即两个对象之间存在has-a关系。但是要注意:一个对象不能自包含,无论是直接的还是间接的,因为编译器无法为它计算sizeof值,也就不知道该给这样的对象分配多少存储空间,见示例8-4。
示例8-4
struct A
{
int i ;
B b ;
};
|
struct B
{
char ch ;
A a ;
};
|
假设A定义在B的前面,于是计算A的大小就需要知道B的大小,而计算B的大小又需要A的大小,……,于是陷入了“鸡生蛋还是蛋生鸡”的怪圈!这样的代码在编译的时候肯定通不过。
虽然对象不能自包含,但可以自引用,而且两个类型可以交叉引用,这种关系称为holds-a关系。因为任何类型的指针的大小都一样,给指针分配存储空间的时候不需要知道它指向的对象的类型细节,见示例8-5。
示例8-5
struct A
{
int count ;
char *pName; // A holds-a string
B *pb ; // A holds-a B
};
|
struct B
{
char ch ;
A *pa; // B holds-a A
B *pNext ; // B自引用
};
|
上面的两个结构可以组成一个链表,A是链表头的类型,B是链表节点的类型。通过链表头节点可以遍历整个链表,每个链表节点还可以指向另一个链表,……,这样就形成了一个庞大的链式结构。
利用对象之间的引用关系,我们就可以实现链表、树、队列等复杂的数据结构,或者实现一些复杂的对象管理,比如对象之间的索引和定位。
【提示8-4】:
|
C++和C都支持相同类型对象之间的直接赋值操作(默认的“operator=语义”,就是对象按成员拷贝语义),但是不能直接比较大小和判断是否相等。
|
这是因为,相同类型对象的各数据成员在内存中的布局是一致的,编译器执行默认的位拷贝也是符合赋值操作语义的。而出于对齐(将大小调整到机器字的整数倍)的考虑,每个对象的存储空间中可能会存在填补字节,这些字节单元不会初始化而是具有上次使用留下的“脏值”(随机值)。显然每个对象填补字节的内容是不会相同的。这就是说,如果编译器支持使用逐位比较的默认方法来比较同类型对象,结果肯定是不对的,而有意义的大小关系是与具体应用相关的,显然编译器并不对应用领域的东西做任何假设。例如:
Student a, b;
cout << ((a.ID > b.ID) ? "a larger than b" : "a less than b") << endl;
所以,当默认的赋值语义不能满足我们的要求的时候,就需要定义自己的赋值语义。在C语言中只有定义一些函数来完成这样的功能,而C++则提供了运算符重载机制可以解决赋值和比较等问题。(本质上仍然是函数调用,只是形式不同而已!)
本文节选自《高质量程序设计指南:C++/C语言》
林锐,韩永泉编著
电子工业出版社出版
分享到:
相关推荐
学习高质量编程需要了解这两种语言的基础知识,包括语法、数据类型、控制结构、函数、数组和指针等。 2. **编译器和链接器**:了解编译器和链接器的工作原理以及如何使用它们来编译和构建程序是编写高质量代码的...
林锐在书中详细阐述了C语言的关键概念,包括变量、数据类型、控制结构(如if语句和循环)、函数、指针等。对于初学者,理解和掌握这些基础知识至关重要。同时,书中也对高级话题如内存管理、预处理器和位操作进行了...
1. **C++/C语言基础**:书中首先会介绍C++和C语言的基础语法,包括变量、数据类型、运算符、流程控制语句等,这些都是编写任何程序的基础。此外,还会讲解指针的使用,这是C++/C语言的一大特色,也是理解和掌握高级...
学习C++/C语言,首先要掌握基础语法,包括变量声明、数据类型、运算符以及流程控制语句(如if、switch、for、while)。其次,深入理解指针的概念极其重要,因为指针是C/C++语言中的强大工具,能够实现高效的内存管理...
C语言是系统编程、嵌入式开发以及各种复杂应用的首选语言,它的核心概念包括变量、数据类型、运算符、控制结构(如if-else、for、while循环)、函数等。通过解决C语言的经典例题,学习者可以深入理解这些基本概念,...
1. **基础语法**:包括变量、数据类型、运算符、流程控制语句等,是学习任何编程语言的基础。书中会详细介绍如何正确地声明和使用这些元素,避免常见的陷阱。 2. **函数与程序结构**:讲解如何组织代码,通过函数...
模板是C++中的泛型编程工具,能够创建可重用的代码片段,适用于多种数据类型。书中会详细介绍模板的使用和其在容器、算法库中的应用。 C语言以其简洁、高效著称,是系统编程和底层开发的首选。书中会涵盖C语言的...
1. **基础语法**:C++/C编程的基础始于变量、数据类型、运算符、流程控制(如if-else、switch-case、for、while循环)。这些实例会展示如何声明、初始化和操作不同类型的变量,以及如何利用条件语句和循环结构来实现...
3. 初始化列表:在构造函数中,使用初始化列表而非赋值语句来初始化成员变量,以确保正确的数据类型转换和防止不必要的默认构造函数调用。 4. 拷贝构造函数:处理对象间的复制,确保深拷贝和浅拷贝的区别理解。 5. ...
- C++20:增加了模块化、协程、Concepts、新数据类型(如std::bit_cast)以及改进的并发支持等。 "CCppckwd_jb51.rar"文件可能是一个补充资料,包含了C++和C的关键字列表,这对于理解和记忆语言的关键元素非常有...
类是C++中的核心构造块,它允许我们定义自定义数据类型,并封装数据和操作数据的方法。继承则提供了代码重用和扩展的能力,使得一个类可以从另一个类中继承属性和行为。多态性让不同类的对象可以以统一的方式进行...
1. **基本语法与数据类型**:C++和C语言都具有严格的语法结构,从变量声明、数据类型(如int、char、float、double等)、运算符到控制流(如if-else、switch-case、for、while循环)的使用,这些都是编程的基础。...
1. **变量与数据类型**:C/C++中的基本数据类型包括整型(如int、short、long)、浮点型(如float、double)、字符型(char)以及布尔型(bool)。题目可能涉及声明变量、初始化以及不同类型间的转换。 2. **运算符...
C语言部分,讲解了基本语法、数据类型、控制流、函数、指针等核心概念。特别强调了指针的使用,它是C语言强大而灵活的关键,但也容易导致错误。书中通过实例展示了如何安全有效地使用指针,以实现内存管理、高效算法...
对于初学者而言,数据结构、指针和数组是C语言中至关重要的概念,它们是理解和编写复杂程序的基础。以下将详细介绍这些知识点。 首先,数据结构是组织、管理、存储和检索数据的一种方式。在C语言中,数据结构主要...
这份“C/C++/C#开发手册资料”可能会涵盖这些语言的所有基本概念,高级特性,以及最佳实践,对于学习和提升这三门语言的技能将大有裨益。通过深入研究,开发者可以更好地理解和运用这些语言,从而在软件开发领域取得...
在指南中,可能会详细讲解C语言的基本数据类型(如int、char、float等)、控制结构(如if语句、for循环、while循环)、函数的声明与调用,以及指针的使用。 接下来,C++的面向对象编程(OOP)概念是核心部分。这...
理解变量、数据类型、运算符、控制结构(如if-else、for、while)、函数、指针、数组、结构体等概念是C/C++学习的基石。对于C++,还需掌握类、对象、继承、多态、模板等特性。 “数据结构”是编程中的核心概念,...