一、类型转换
静态转换(Static Cast)
静态转换是将一种数据类型的值强制转换为另一种数据类型的值。
静态转换通常用于比较类型相似的对象之间的转换,例如将 int 类型转换为 float 类型。
静态转换不进行任何运行时类型检查,因此可能会导致运行时错误。
动态转换(Dynamic Cast)
动态转换通常用于将一个基类指针或引用转换为派生类指针或引用。动态转换在运行时进行类型检查,如果不能进行转换则返回空指针或引发异常。
常量转换(Const Cast)
常量转换用于将 const 类型的对象转换为非 const 类型的对象。
常量转换只能用于转换掉 const 属性,不能改变对象的类型。
重新解释转换(Reinterpret Cast)
重新解释转换将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间进行转换。
重新解释转换不进行任何类型检查,因此可能会导致未定义的行为。
int i = 10;
float f = static_cast<float>(i); // 静态将int类型转换为float类型
class Base {};
class Derived : public Base {};
Base* ptr_base = new Derived;
Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base); // 将基类指针转换为派生类指针
const int i = 10;
int& r = const_cast<int&>(i); // 常量转换,将const int转换为int
int i = 10;
float f = reinterpret_cast<float&>(i); // 重新解释将int类型转换为float类型
二、左值(Lvalues)和右值(Rvalues)
C++ 中有两种类型的表达式:
- 左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
- 右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。
变量是左值,因此可以出现在赋值号的左边。数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。下面是一个有效的语句:
int g = 20;
但是下面这个就不是一个有效的语句,会生成编译时错误:
10 = 20;
三、初始化局部变量和全局变量
当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动初始化。
四、C++ 中的类型限定符
const | const 定义常量,表示该变量的值不能被修改。 |
volatile | 修饰符 volatile 告诉该变量的值可能会被程序以外的因素改变,如硬件或其他线程。。 |
restrict | 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。 |
mutable | mutable 用于修饰类的成员变量。被 mutable 修饰的成员变量可以被修改,即使它们所在的对象是 const 的。 |
static | 用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问。 |
register | 用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。 |
五、C++存储类
-
auto:这是默认的存储类说明符,通常可以省略不写。auto 指定的变量具有自动存储期,即它们的生命周期仅限于定义它们的块(block)。auto 变量通常在栈上分配。
-
register:用于建议编译器将变量存储在CPU寄存器中以提高访问速度。在 C++11 及以后的版本中,register 已经是一个废弃的特性,不再具有实际作用。
-
static:用于定义具有静态存储期的变量或函数,它们的生命周期贯穿整个程序的运行期。在函数内部,static变量的值在函数调用之间保持不变。在文件内部或全局作用域,static变量具有内部链接,只能在定义它们的文件中访问。
-
extern:用于声明具有外部链接的变量或函数,它们可以在多个文件之间共享。默认情况下,全局变量和函数具有 extern 存储类。在一个文件中使用extern声明另一个文件中定义的全局变量或函数,可以实现跨文件共享。
-
mutable (C++11):用于修饰类中的成员变量,允许在const成员函数中修改这些变量的值。通常用于缓存或计数器等需要在const上下文中修改的数据。
-
thread_local (C++11):用于定义具有线程局部存储期的变量,每个线程都有自己的独立副本。线程局部变量的生命周期与线程的生命周期相同。
六、C++ 引用 vs 指针
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
七、C++输入输出
标准输出流(cout)
预定义的对象 cout 是 iostream 类的一个实例。cout 对象"连接"到标准输出设备,通常是显示屏。cout 是与流插入运算符 << 结合使用的
标准输入流(cin)
预定义的对象 cin 是 iostream 类的一个实例。cin 对象附属到标准输入设备,通常是键盘。cin 是与流提取运算符 >> 结合使用的
标准错误流(cerr)
预定义的对象 cerr 是 iostream 类的一个实例。cerr 对象附属到标准输出设备,通常也是显示屏,但是 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。cerr 是与流插入运算符 << 结合使用的
标准日志流(clog)
预定义的对象 clog 是 iostream 类的一个实例。clog 对象附属到标准输出设备,通常也是显示屏,但是 clog 对象是缓冲的。这意味着每个流插入到 clog 都会先存储在缓冲区,直到缓冲填满或者缓冲区刷新时才会输出。clog 是与流插入运算符 << 结合使用的
八、结构体与类的区别
在 C++ 中,struct 和 class 本质上非常相似,唯一的区别在于默认的访问权限:
struct
默认的成员和继承是public
。class
默认的成员和继承是private
。
你可以将 struct
当作一种简化形式的 class
,适合用于没有太多复杂功能的简单数据封装。
九、C++ vector 容器
vector简介:
C++ 中的 vector 是一种序列容器,它允许你在运行时动态地插入和删除元素。
vector 是基于数组的数据结构,但它可以自动管理内存,这意味着你不需要手动分配和释放内存。
与 C++ 数组相比,vector 具有更多的灵活性和功能,使其成为 C++ 中常用的数据结构之一。
vector 是 C++ 标准模板库(STL)的一部分,提供了灵活的接口和高效的操作。
基本特性:
- 动态大小:
vector
的大小可以根据需要自动增长和缩小。 - 连续存储:
vector
中的元素在内存中是连续存储的,这使得访问元素非常快速。 - 可迭代:
vector
可以被迭代,你可以使用循环(如for
循环)来访问它的元素。 - 元素类型:
vector
可以存储任何类型的元素,包括内置类型、对象、指针等。
使用容器:
创建:
std::vector<int> myVector; // 创建一个存储整数的空 vector
std::vector<int> myVector(5); // 创建一个包含 5 个整数的 vector,每个值都为默认值(0)
std::vector<int> myVector(5, 10); // 创建一个包含 5 个整数的 vector,每个值都为 10
std::vector<int> myVector{10,10,10,10,10}; // 创建一个包含 5 个整数的 vector,每个值都为 10
操作:
myVector.push_back(7); // 将整数 7 添加到 vector 的末尾
myVector.erase(myVector.begin() + 2); // 删除第三个元素
myVector.clear(); // 清空 vector
int size = myVector.size(); // 获取 vector 中的元素数量
访问:
int x = myVector[0]; // 获取第一个元素
int y = myVector.at(1); // 获取第二个元素
// 遍历 vector 中的元素
for (auto it = myVector.begin(); it != myVector.end(); ++it) {
std::cout << *it << " ";
}
for (int element : myVector) {
std::cout << element << " ";
}
十、C++数据结构
1. 数组(Array)
数组是最基础的数据结构,用于存储一组相同类型的数据。
特点:
- 固定大小,一旦声明,大小不能改变。
- 直接访问元素,时间复杂度为 O(1)。
- 适合处理大小已知、元素类型相同的集合。
优缺点:
- 优点:访问速度快,内存紧凑。
- 缺点:大小固定,无法动态扩展,不适合处理大小不确定的数据集。
2. 结构体(Struct)
结构体允许将不同类型的数据组合在一起,形成一种自定义的数据类型。
特点:
- 可以包含不同类型的成员变量。
- 提供了对数据的基本封装,但功能有限。
3. 类(Class)
类是 C++ 中用于面向对象编程的核心结构,允许定义成员变量和成员函数。与 struct
类似,但功能更强大,支持继承、封装、多态等特性。
特点:
- 可以包含成员变量、成员函数、构造函数、析构函数。
- 支持面向对象特性,如封装、继承、多态。
4. 链表(Linked List)
链表是一种动态数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。
特点:
- 动态调整大小,不需要提前定义容量。
- 插入和删除操作效率高,时间复杂度为 O(1)(在链表头部或尾部操作)。
- 线性查找,时间复杂度为 O(n)。
优缺点:
- 优点:动态大小,适合频繁插入和删除的场景。
- 缺点:随机访问效率低,不如数组直接访问快。
5. 栈(Stack)
栈是一种后进先出(LIFO, Last In First Out)的数据结构,常用于递归、深度优先搜索等场景。
特点:
- 只允许在栈顶进行插入和删除操作。
- 时间复杂度为 O(1)。
优缺点:
- 优点:操作简单,效率高。
- 缺点:只能在栈顶操作,访问其他元素需要弹出栈顶元素。
6. 队列(Queue)
队列是一种先进先出(FIFO, First In First Out)的数据结构,常用于广度优先搜索、任务调度等场景。
特点:
- 插入操作在队尾进行,删除操作在队头进行。
- 时间复杂度为 O(1)。
优缺点:
- 优点:适合按顺序处理数据的场景,如任务调度。
- 缺点:无法随机访问元素。
7. 双端队列(Deque)
双端队列允许在两端进行插入和删除操作,是栈和队列的结合体。
特点:
- 允许在两端进行插入和删除。
- 时间复杂度为 O(1)。
优缺点:
- 优点:灵活的双向操作。
- 缺点:空间占用较大,适合需要在两端频繁操作的场景。
8. 哈希表(Hash Table)
哈希表是一种通过键值对存储数据的数据结构,支持快速查找、插入和删除操作。C++ 中的 unordered_map
是哈希表的实现。
特点:
- 使用哈希函数快速定位元素,时间复杂度为 O(1)。
- 不保证元素的顺序。
优缺点:
- 优点:查找、插入、删除操作效率高。
- 缺点:无法保证元素顺序,哈希冲突时性能会下降。
9. 映射(Map)
map
是一种有序的键值对容器,底层实现是红黑树。与 unordered_map
不同,它保证键的顺序,查找、插入和删除的时间复杂度为 O(log n)。
特点:
- 保证元素按键的顺序排列。
- 使用二叉搜索树实现。
优缺点:
- 优点:元素有序,适合需要按顺序处理数据的场景。
- 缺点:操作效率比
unordered_map
略低。
10. 集合(Set)
set
是一种用于存储唯一元素的有序集合,底层同样使用红黑树实现。它保证元素不重复且有序。
特点:
- 保证元素的唯一性。
- 元素自动按升序排列。
- 时间复杂度为 O(log n)。
优缺点:
- 优点:自动排序和唯一性保证。
- 缺点:插入和删除的效率不如无序集合。
11. 动态数组(Vector)
vector
是 C++ 标准库提供的动态数组实现,可以动态扩展容量,支持随机访问。
特点:
- 动态调整大小。
- 支持随机访问,时间复杂度为 O(1)。
- 当容量不足时,动态扩展,时间复杂度为摊销 O(1)。
优缺点:
- 优点:支持随机访问,动态扩展。
- 缺点:插入和删除中间元素的效率较低。
十一、类 & 对象
1、类的定义
类的基本结构:
可以在类的外部使用范围解析运算符 :: 定义该函数:
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void);
};
double Box::getVolume(void)
{
return length * breadth * height;
}
2、对象的定义
对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样:
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
类的对象的公共数据成员可以使用直接成员访问运算符 . 来访问。
3、类访问修饰符
公有(public)成员:公有成员在程序中类的外部是可访问的,可以不使用任何成员函数来设置和获取公有变量的值。
私有(private)成员:私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。默认情况下,类的所有成员都是私有的。
protected(受保护)成员:protected(受保护)成员变量或函数与私有成员十分相似,但有一点不同,protected(受保护)成员在派生类(即子类)中是可访问的。
4、构造函数与析构函数
构造函数:
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。构造函数名称与类名一致,参数不做要求,一般参数用来接收数据来初始化成员变量。
使用初始化列表来初始化字段:
C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
....
}
上面代码等价与下面代码:
C::C( double A, double B, double C): X(a), Y(b), Z(c)
{
A=a;
B=b;
C=c;
....
}
拷贝构造函数:
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
-
通过使用另一个同类型的对象来初始化新创建的对象。
-
复制对象把它作为参数传递给函数。
-
复制对象,并从函数返回这个对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:
classname (const classname &obj) {
// 构造函数的主体
}
如下代码有两个地方调用了拷贝构造函数,一个是创建对象 line1 后,在将 line1 的值赋给 line2 时调用拷贝构造函数,在拷贝构造函数中将 line1 的值传给 line2;另一个是在将对象作为参数传入另一个函数时调用了拷贝构造函数,拷贝出一个形参用于调用函数的使用。
int main( )
{
Line line1(10);
Line line2 = line1; // 这里也调用了拷贝构造函数
display(line2);
return 0;
}
析构函数:
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
5、友元函数(friend)
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend。
友元函数没有 this 指针,因为友元不是类的成员,只有成员函数才有 this 指针。
6、内联函数(inline)
C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。
在类中定义并且实现的函数都是内联函数,即使没有使用 inline 说明符。
7、类的静态成员(static)
使用 static 关键字来把类成员定义为静态的。静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
class Box
{
public:
static int objectCount;
// 构造函数定义
Box()
{
objectCount++;
}
static int getCount()
{
return objectCount;
}
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
十二、继承
当创建一个类时,不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
class derived-class: access-specifier base-class
// 派生类
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
// 派生类
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
继承的特点:
1、有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。如果未使用访问修饰符,则默认为 private。
-
public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
-
protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
-
private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private
2、但无论哪种继承方式private 成员只能被本类成员(类内)和友元访问,不能被派生类访问。
3、一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数
- 基类的重载运算符
- 基类的友元函数
十三、重载
1、重载函数
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
2、重载运算符
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
Box operator+(const Box&);
class Box
{
public:
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 程序的主函数
int main( )
{
……
// 把两个对象相加,得到 Box3
Box3 = Box1 + Box2;
……
}
十四、多态
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
子类对父类函数的独立实现。这就是多态的一般使用方式。该函数可以与父类带有同一个名称但具有不同的实现,函数的参数甚至可以是相同的。
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// 程序的主函数
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area();
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Parent class area : Parent class area :
导致错误输出的原因是,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。
当我们在 Shape 类中,area() 的声明前放置关键字 virtual 后结果就正常了。
虚函数:
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
纯虚函数:
您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
我们可以把基类中的虚函数 area() 改写如下:
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
// pure virtual function
virtual int area() = 0;
};
十五、封装
数据封装(Data Encapsulation)是面向对象编程(OOP)的一个基本概念,它通过将数据和操作数据的函数封装在一个类中来实现。这种封装确保了数据的私有性和完整性,防止了外部代码对其直接访问和修改。
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
十六、文件操作
文件操作用到 C++ 中另一个标准库 fstream,它定义了三个新的数据类型:
数据类型 | 描述 |
---|---|
ofstream | 该数据类型表示输出文件流,用于创建文件并向文件写入信息。 |
ifstream | 该数据类型表示输入文件流,用于从文件读取信息。 |
fstream | 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。 |
1、打开文件:
void open(const char *filename, ios::openmode mode);
第二个参数定义文件被打开的模式:
模式标志 | 描述 |
---|---|
ios::app | 追加模式。所有写入都追加到文件末尾。 |
ios::ate | 文件打开后定位到文件末尾。 |
ios::in | 打开文件用于读取。 |
ios::out | 打开文件用于写入。 |
ios::trunc | 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。 |
2、关闭文件:
void close();
3、写入文件:
在 C++ 编程中,我们使用流插入运算符( << )向文件写入信息,就像使用该运算符输出信息到屏幕上一样。唯一不同的是,在这里您使用的是 ofstream 或 fstream 对象,而不是 cout 对象。
4、读取文件:
在 C++ 编程中,我们使用流提取运算符( >> )从文件读取信息,就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里您使用的是 ifstream 或 fstream 对象,而不是 cin 对象。
5、文件位置指针:
istream 和 ostream 都提供了用于重新定位文件位置指针的成员函数。这些成员函数包括关于 istream 的 seekg("seek get")和关于 ostream 的 seekp("seek put")。
seekg 和 seekp 的参数通常是一个长整型。第二个参数可以用于指定查找方向。查找方向可以是 ios::beg(默认的,从流的开头开始定位),也可以是 ios::cur(从流的当前位置开始定位),也可以是 ios::end(从流的末尾开始定位)。
文件位置指针是一个整数值,指定了从文件的起始位置到指针所在位置的字节数。下面是关于定位 "get" 文件位置指针的实例:
ofstream outfile;
ifstream infile;
// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
infile.seekg( n );
// 把文件的写指针从 outfile 当前位置向后移 n 个字节
outfile.seekp( n, ios::cur );
// 把文件的读指针从 infile 末尾往回移 n 个字节
infile.seekg( n, ios::end );
// 定位到 outfile 的末尾
outfile.seekp( 0, ios::end );
6、实例:
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
char data[100];
// 以写模式打开文件
ofstream outfile;
outfile.open("afile.dat");
cout << "Writing to the file" << endl;
cout << "Enter your name: ";
cin.getline(data, 100);
// 向文件写入用户输入的数据
outfile << data << endl;
cout << "Enter your age: ";
cin >> data;
cin.ignore();
// 再次向文件写入用户输入的数据
outfile << data << endl;
// 关闭打开的文件
outfile.close();
// 以读模式打开文件
ifstream infile;
infile.open("afile.dat");
cout << "Reading from the file" << endl;
infile >> data;
// 在屏幕上写入数据
cout << data << endl;
// 再次从文件读取数据,并显示它
infile >> data;
cout << data << endl;
// 关闭打开的文件
infile.close();
return 0;
}
十七、异常
异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw。
- throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
- catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
- try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
抛出异常
可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
以下是尝试除以零时抛出异常的实例:
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
捕获异常
catch 块跟在 try 块后面,用于捕获异常。您可以指定想要捕捉的异常类型,这是由 catch 关键字后的括号内的异常声明决定的。
try
{
// 保护代码
}catch( ExceptionName e )
{
// 处理 ExceptionName 异常的代码
}
上面的代码会捕获一个类型为 ExceptionName 的异常。如果您想让 catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 ...,如下所示:
try
{
// 保护代码
}catch(...)
{
// 能处理任何异常的代码
}
十八、动态内存分配
new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。
数组:
char* pvalue = NULL; // 初始化为 null 的指针
pvalue = new char[20]; // 为变量请求内存
对象:
如果要为一个包含四个 Box 对象的数组分配内存,构造函数将被调用 4 次,同样地,当删除这些对象时,析构函数也将被调用相同的次数(4次)。
class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
……
Box* myBoxArray = new Box[4];
……
delete [] myBoxArray; // 删除数组
……
}
十九、命名空间
命名空间用来区分不同库中相同名称的函数、类、变量等。
命名空间的定义使用关键字 namespace,后跟命名空间的名称,为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称。命名空间支持嵌套。
namespace namespace_name {
// 代码声明
code
namespace namespace_name1 {
// 代码声明
code1
}
}
namespace_name::code; // code 可以是变量或函数
namespace_name::namespace_name1::code1;
可以使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。
using namespace std;
using 指令也可以用来指定命名空间中的特定项目。例如,如果只打算使用 std 命名空间中的 cout 部分,可以使用如下的语句。随后的代码中,在使用 cout 时就可以不用加上命名空间名称作为前缀,但是 std 命名空间中的其他项目仍然需要加上命名空间名称作为前缀。
using std::cout;
二十、模版
对于向量,我们可以定义许多不同类型的向量,比如 vector <int> 或 vector <string>。但我们不需要每个类型都写一个函数,可以使用模板来定义。
函数模版
比如下面一个比大小的函数模版,以关键字template开始,后跟一个模板参数列表,这是一个逗号分隔的一个或多个模板参数的列表,用小于号(<)和大于号(>)包围起来。它会根据函数传入的实参来推断模板实参。
比如执行该代码:cout << compare(1, 0) << endl; 实参为int型,编译器会将模板实参推断为int,并将它绑定到模版参数 T。在函数体内,我们可以像使用任何其他类型一样使用T
。
template<typename T>
int compare(const T& v1, const T& v2)
{
if (v1 < v2)return -1;
if (v2 < v1)return 1;
return 0;
}
类模版
类似函数模版,类模版以关键字template开始,后跟模版参数列表。
template <typename T1, typename T2>
class Pair {
private:
T1 first;
T2 second;
public:
Pair(const T1& f, const T2& s) : first(f), second(s) {}
T1 getFirst() const { return first; }
T2 getSecond() const { return second; }
};
// 实例化了一个 Pair 对象,其中 T1 被实例化为int,T2 被实例化为 double
Pair<int, double> myPair(5, 3.14);
二十一、信号处理
信号是由操作系统传给进程的中断,会提早终止一个程序。下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++ 头文件 <csignal> 中。
信号 | 描述 |
---|---|
SIGABRT | 程序的异常终止,如调用 abort。 |
SIGFPE | 错误的算术运算,比如除以零或导致溢出的操作。 |
SIGILL | 检测非法指令。 |
SIGINT | 程序终止(interrupt)信号。 |
SIGSEGV | 非法访问内存。 |
SIGTERM | 发送到程序的终止请求。 |
signal() 函数
C++ 信号处理库提供了 signal 函数,用来捕获突发事件。
signal(registered signal, signal handler)
这个函数接收两个参数:第一个参数是要设置的信号的标识符,第二个参数是指向信号处理函数的指针。函数返回值是一个指向先前信号处理函数的指针。如果先前没有设置信号处理函数,则返回值为 SIG_DFL。如果先前设置的信号处理函数为 SIG_IGN,则返回值为 SIG_IGN。
raise() 函数
您可以使用函数 raise() 生成信号,该函数带有一个整数信号编号作为参数:
int raise (signal sig);
sig 是要发送的信号的编号,这些信号包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。
实例:
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
void signalHandler( int signum )
{
cout << "Interrupt signal (" << signum << ") received.\n";
// 清理并关闭
// 终止程序
exit(signum);
}
int main ()
{
int i = 0;
// 注册信号 SIGINT 和信号处理程序
signal(SIGINT, signalHandler);
while(++i){
cout << "Going to sleep...." << endl;
if( i == 3 ){
raise( SIGINT);
}
sleep(1);
}
return 0;
}