用户自定义类型
可以通过基本类型、const修饰符和声明运算符构造出的类型称为内置类型。内置类型的优点是能够直接有效地展现出传统计算机硬件的特性,但并不能向程序员提供便于书写高级应用程序的上层特性。
结构
构建新类型的第一步通常是把所需要的元素组织成一种数据结构。
new 运算符从一块名为自由存储(free store) (又称为动态内存 (dynamic memory) 或堆 (heap) ) 的区域中分配内存。
类
把类型的接口(所有代码都可使用的部分)与其现实(对其他不可访问的数据具有访问权限)分离。 C++中实现上述的语言机制被称为类。
类含有一系列成员,可能是数据、函数或者类型
- 类的
public 成员定义该类的接口 private 成员则只能通过接口访问。
class Vector {
public:
Vector(int s): elem{new double[s]}, sz{s} {}
double& operator[](int i) { return elem[i]; }
itn size() { return sz; }
private:
double* elem;
int sz;
}
枚举
枚举:简单形式用户自定义类型,使得我们可以枚举一系列值。
enum class Color{red, blue, green };
enum class Traffic_light {green, yellow, red};
Color col = Color::red;
Traffic_light light = Traffic_light::red;
枚举类型常用于描述规模较小的整数值合集。通过使用有指代意义的(且易于记忆的)枚举值名字可提高代码的可读性,降低出错的风险。
通过枚举可以在不同的枚举中重复使用枚举值,不至于引起混淆,例如:Color::red 与 traffic_light::red。
enum 后面的 class 指明了枚举是强类型的,且他的枚举值位于指定的作用域中。不同的 enum class 是不同的类型,这有助于防止对常量的意外误用
Color x = red;
Color y = Traffic_light::red;
Color z = Color::red;
int i = Color::red;
Color c = 2;
如果不想显示地限定枚举值名字, 并且希望枚举值可以是 int (无需显示转换),则应该去掉 enum class 中的 class 得到一个“普通的”enum。
默认情况下,enum class 只定义了赋值、初始化和比较(==, <)操作。枚举类型设计一种用户自定义类型,我们可以为它定义别的运算符
Traffic_light& operator++(Traffic_light& t)
{
switch (t) {
case Traffic_light::green:
return t = Traddic_light::yellow;
case Traffic_light::yellow:
return t = Traffic_light::red;
case Traffic_light::red:
return t = Traffic_light::green;
}
}
模块化
一个 C++ 程序可能包含许多独立开发的部分,例如函数、用户自定义类型、类层次和模板等。因此构建C+chengxu的关键就是清晰地定义这些组成部分之间的交互关系。第一部也是最重要的一步,是将某个部分的接口和实现分离开来。
==在 C++ 语言中用声明描述接口。
声明指定了使用某个函数或某种类型所需的所有内容==
分离编译
用户代码只能看见所用类型和函数的声明,他们的定义则放置再分离得源文件里,并分别编译
这种机制有助于将一个程序组织成一组版独立的代码片段。 优点:
- 编译时间减到最少
- 强制要求程序中逻辑独立的部分分离开来(从而将发生错误的几率降到最低)
- 一个库通常是一组分离编译的代码片段的集合
严格来说,使用分离编译并不是一个语言问题;而是关门与如何以最佳方式利用特定语言实现的问题。
分离机制在实际的编程过程中非常重要
最好的方法是最大限度地模块化,逻辑上通过语言特性描述模块,而后在物理上通过划分文件及高校分离编译来从分利用模块化。
#pragma once
#include <iostream>
using namespace std;
class Vector {
public:
Vector(int s);
double& operator[](int i);
int size();
private:
double* elem;
int sz;
};
#include "Vector.h"
Vector::Vector(int s)
:elem{ new double[s] }, sz{ s }
{
}
double& Vector::operator[](int i)
{
return elem[i];
}
int Vector::size()
{
return sz;
}
名字空间
- 表达某些声明是属于一个整体的,
- 表明他们的名字不会与其他名字空间中的名字冲突。
名字空间主要用于组织较大规模的程序组件,最典型的例子是库。使用名字空间,我们就可以很容易地把若干独立开发的部件组织成一个程序。
错误处理
错误处理是一个略显繁杂的主题,他的内容和影响都远远超越了语言特性的层面。***(非常重要)***
在构建应用程序时,通常的做法不是仅仅依靠内置类型(如 char、int、和 double)和语句(如 if、while 和 for),而是建立更多适合应用的新类型(如 string、map 和 regex)和算法(如 sort()、find_if() 和 draw_all() ).**这些高层次的结构简化了程序设计,减少了产生错误的机会,同时也增加了编译器捕获错误的概率。
异常
处理异常由实现者检测可能的异常,然后通知使用者,让使用者可以采取适当的措施。例如vector::operator能够检测潜在的访问错误并且抛出一个 out_of_range 异常
double& Vector::operator[](int i)
{
if (i < 0 || size() <= i) throw out_of_range{"Vector::operator[]"};
return elem[i];
}
把可能处理异常的程序放在一个 try 块当中。
void f(Vector& v)
{
try{
v[v.size()] = 7;
}
catch (out_of_range) {
}
}
不变式
假定某事为真的声明称为类的不变式,简称不变式。建立类的不变式是构造函数的任务,他的另一个作用是确保当成员函数退出时不变式仍然成立。
不变式的概念是设计类的关键,而前置条件也在设计函数的过程中起到同样的作用。不变式:
- 帮助我们准确地理解想要什么;
- 强制要求具体而明确地描述设计,而这有助于确保代码正确
静态断言
把表达某种期望的语句称为断言(assertion)例如: static_assert(4 <= sizeof(int), "intefers are roo small"); // 检查整数的尺寸 如果 4 <= sizeof(int) 不满足,将输出 intergers are too small 的信息。
== static_assert 机制能用于任何可以表达为常量表达式的东西==
constexpr double C = 299792.458;
void f(double speed)
{
const double local_max = 160.0 / (60 * 60);
statuic_assert(speed < C, "can't go that fast");
static_assert(local_max < C, "can't go that fast")
}
==static_assert 最后总要的用途是为泛型编程中作为形参的类型设置断言
|