struct简介
在 C 语言中,关键字 struct 是程序员可以定义一块结构化的内存,用于存储具有各种各样数据类型的一个数据集。
struct Fraction {
int number,denom;
char description[256];
};
struct 中的每一个小型内存块 (number, denom, Description) 都可以通过名称访问。这些小型的内存块被称为数据成员,有时也被称为字段。
类定义
C++ 语言有另外一种类型与 struct 相似的数据类型,称为类(Class)。
class ClassName {
public:
publicMembers;
private:
privateMembers;
};
类的特性包括数据成员、成员函数以及访问限定符。成员函数用来初始化、操作或管理数据成员。
在定义了一个类之后,其类名称就可以作为变量、参数使用。类的类型变量被称为这个类的对象或者实例。
ClassName 类的成员函数指定了此类型所有对象的行为。每一个成员函数都能访问类中的其它成员。非成员函数只能通过调用成员函数间接的操作对象。
头文件
为了定义类(或者任何其它类型的数据结构),应将它的定义放在头文件中,这个文件一般和类同名,后缀为 .h
预处理器会将头文件包含到其它的文件中。为了防止头文件在某个编译过的文件中被错误的包含多次,一般使用
#ifndef
...
#define
...
#endif
预处理器宏来将头文件包裹起来
一般而言,应当将成员函数的定义置于类定义的外面,将其放置到一个单独的实现文件中,其文件扩展名为 .cpp
类作用域包含整个类定义,不管整个类定义中在哪里声明成员名称。它也扩展到实现文件 (.cpp
文件)。位于类定义之外的任何类成员的定义,都要求在名称前面有一个形式为ClassName::
的作用域解析运算符。作用域解析运算符告诉编译器,类的作用域扩展到类定义之外,包含了位于符号::
和函数定义的一对大括号内的代码。
通常而言,类定义不应该包含哪些显示或传输数据成员值的成员函数。比如:display(), readFromFile()。
定义在类的外面,比如普通的成员函数,或者具有包含关系的类定义中。
成员访问限定符
访问限定符 public,protected 和 private 用于类定义中:
- 只要一个程序包含了某个类的定义文件,就可以在程序的任何位置 (通过此类的对象) 访问 public 成员
- protected 成员只能在本类的成员函数或派生类的成员函数的定义中访问。
- private 成员仅仅允许在本类的成员函数中访问。
默认情况下,类成员是 private 类型的。如果类定义的成员声明前面没有访问修饰符,则成员就是 private 类型的。
可访问性与可见性
可访问性,指可以读写它。可见性,指某个项在其作用域中都是可见的。private 修改的变量只有可见性,没有可访问性。
封装
封装是面向对象编程中的第一个概念性步骤
- 将数据和操作数据的函数一起封装在适当命名的类中
- 提供命名清晰、文档良好的 public 函数,是类的用户能够对这个类的对象做任何需要做的事情
- 隐藏实现细节
类中的 public 函数的原型集合被称为这个类的 public 接口。非 public 类成员的集合加上这些成员函数的定义被称为类的实现。
UML介绍
现代的、面向对象的应用是以设计良好的类为基础的。在大多数工程中,通用用途的库(比如:Qt)和特定任务的库就提供这样的类。程序员提供其他的类。UML(Unified Modeling Language,统一建模语言)是用于面向对象设计的最常用语言。它使设计者能够用丰富的框架描述工程。
UML关系
由于这两个 point 数据成员是类对象,所以它们被认为是 Square 类的子对象,而 Square 对象是它的 Point 子对象的父对象。当删除 Square 对象时,其子对象也会被删除。这使得子对象成为父对象的一部分,它们之间的关系被称为“组合”(composition)。图中,实心菱形表明这个类的实例与关系连接线另一端的那个类的实例是组合关系。
类的友元
友元机制允许非成员函数访问一个类的私有数据。关键字 friend 可以放在类声明或函数声明之前,友元声明位于类定义之内。
构造函数
构造函数是一种控制对象初始化过程的特殊成员函数。构造函数必须与它的类同名。它不返回任何值,也不存在返回类型。
ClassName::ClassName(parameterList)
:initList
{
construct body
}
在参数表的右括号和函数体的左大括号之间,可以提供一个可选的初始化类表。成员初始化表以一个冒号开始,后面是一个以逗号分隔的成员初始化列表,其形式如下:
menberName(initializingExpression);
当且仅当,类的定义中没有提供任何构造函数时,编译器会提供一个默认的构造函数。默认构造函数会对它的类对象进行默认初始化。任何没有在成员初始化表中明确初始化的数据成员,都会被编译器赋予默认的初始值。
如果不使用成员初始化列表,则每一个数据成员首先都会包含默认初始值,然后被赋予一个值。这样做不会有错误的发生,但初始化会浪费处理时间。
析构函数
析构函数是一种特殊的成员函数,会在对象销毁之前进行自动清理工作。
对象何时销毁:
- 当一个局部对象超出其作用域时(例如:函数返回)
- 通过 new 运算符创建的对象使用delete运算符销毁时
- 当程序终止,所有静态存储的对象都会被销毁
析构函数的名称是一个波浪号(~)开头再加类名称。它没有返回类型,也没有任何参数。因此,类只能有一个析构函数。如果类中没有析构函数的定义,则编译器会提供一个默认的析构函数,如下:
ClassName::~ClassName() {}
由编译器生成的默认析构函数,会在此类的对象被销毁之前调用该类所有成员的析构函数,调用时按照成员在类定义中出现的次序进行。当销毁指针成员时,默认构造函数不会删除所分配的内存。
static关键字
-
static 类成员
static 数据成员是一块与类本身相关联的数据,而不是属于某个特定的对象。类的每个对象都维护属于自己的非 static 数据成员,而任何 static 数据成员都只有一个实例,并且被该类的所有对象共享。
相对于全局变量,我们更倾向于使用 static 数据成员(且一般可以代替全局变量),因为 static 成员不会在全局命名空间中添加不必要的名称。全局命名空间污染
将名称添加到全局作用域中(例如,通过声明全局变量或者全局函数),被称为全局命名污染,这被认为是一种不好的编程风格,其中之一是它会增加名称冲突和名称混淆的可能性。每一个 static 数据成员都必须在类定义之外的文件中被初始化,合适的地方为类实现文件。即 cpp 源文件。
-
块作用域内的 static 变量
在一个函数或者代码块中定义的 static 变量,会在第一次执行时进行初始化。一直存在,并在多次调用之间拥有上次调用的值。 -
static 变量初始化
不在代码块或者函数内定义的 static 对象,在其相应的对象模块首次加载时会进行初始化。通常是在程序启动之后,main 函数启动之前。
static 对象只会构建一次,并保持到程序终止。
拷贝构造函数与赋值运算符
对象的“生命周期”管理意味着完全控制对象诞生、繁殖和消亡的过程。
拷贝构造函数是一种构造函数,其原型如下:
ClassName(const ClassName& x);
拷贝构造函数的作用是创建一个对象,该对象是同一个类中已有对象的精确副本。
针对某个类的赋值运算符重载了符号=,其含义对特定的类都不相同。原型:
ClassName& operator= (const ClassName& x);
编译器提供的版本
如果没有定义类的拷贝构造函数和赋值运算符函数,编译器就会提供它们的默认版本。编译器提供的版本为 public 类型,对于类T的默认版本的原型如下:
T::T(const T& other);
T& T::operator= (const T& other);
这两个默认函数都会精确复制每一个数据成员的值。对于其数据类型都为简单类型或者值类型的类,比如int,double,String 等。但是,如果具有指针对象或者对象成员。则需要同时编写这个类特别设计的拷贝构造函数和赋值运算符函数。
const成员函数
要知道每一个非 static 成员函数都有一个称为 this 的隐式参数,this 原本是一个指向主对象的指针。当将一个成员函数声明为 const 时,就相当于告诉只要此函数被调用,this 就是一个指向 const 的指针。