参考自 菜鸟教程
文件和流
对文件的操作需要使用C++ 中另一个标准库 fstream,它定义了3种新的数据类型
打开文件
使用open函数打开文件,指定文件名和打开模式
void open(const char *filename, ios::openmode mode);
ifstream的默认打开模式是ios::in,ofstream的默认打开模式是ios::out,fstream的默认打开模式是ios::in||ios::out
有以下几种打开模式
示例:使用ofstream打开文件,模式为写入+截断
#include <fstream>
using namespace std;
ofstream file;
file.open("file.txt",ios::out||ios::trunc);
//或者不指定打开模式 file.open("file.txt");
关闭文件
使用close函数关闭文件
void close();
读写文件
将cout替换为ofstream/ifstream/fstream的对象,使用<<写文件,使用<<读文件
读写实例:
#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;
}
文件位置指针
重新定位文件位置指针的成员函数:seekg(设置输入流的文件指针),seekp(设置输出流的文件指针)
// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
fileObject.seekg( n );
// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg( n, ios::cur );
// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg( n, ios::end );
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
异常
异常处理涉及3个关键字:
- throw:抛出异常
- catch:捕捉异常
- try:try块放置可能抛出异常的代码
实例
#include <iostream>
using namespace std;
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
int main ()
{
int x = 50;
int y = 0;
double z = 0;
try {
z = division(x, y);
cout << z << endl;
}catch (const char* msg) {
cerr << msg << endl;
}
return 0;
}
如果想让catch捕捉所以异常,可以使用…
try
{
// 保护代码
}catch(...)
{
// 能处理任何异常的代码
}
exception类
std::exception是所有标准 C++ 异常的父类。
示例:
#include <iostream>
#include <exception>
using namespace std;
struct MyException : public exception
{
//what ()函数,用于获取描述异常的 C 风格字符串
//const 和throw ()是函数的修饰符。const:函数不会修改调用它的对象的状态,
//throw():函数不抛出任何异常,等价于noexcept
const char * what () const throw ()
{
return "C++ Exception";
}
};
int main()
{
try
{
throw MyException();
}
catch(MyException& e)
{
std::cout << "MyException caught" << std::endl;
std::cout << e.what() << std::endl;
}
catch(std::exception& e)
{
//其他的错误
}
}
动态内存
C++ 程序中的内存分为两个部分:
- 在函数内部声明的所有变量都将占用栈内存。new关键字创建的变量除外。
- 这是程序中未使用的内存,在程序运行时可用于动态分配内存。
new和delete
- new运算符为任意类型的变量分配处于堆的动态内存,并返回变量地址。
double* pvalue = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量请求内存
- 如果new运算符返回NULL,则表示是自由存储区已被用完,无法分配内存。
- delete:使用 delete 操作符释放它所占用的内存
delete pvalue
数组的动态内存分配
一维数组:
int *array = new int[20];
delete [] array;
二维数组:
//动态分配int[n][m]的内存
int **array = new int *[n]
for(int i = 0;i<n;i++){
array[i] = new int[m];
}
//释放
for(int i = 0;i<n;i++){
delete [] array[i]
}
delete [] array;
对象的动态内存分配
一个示例:对象数组的内存分配和释放
#include <iostream>
using namespace std;
class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
Box* myBoxArray = new Box[4];
delete [] myBoxArray; // 删除数组
return 0;
}
命名空间
命名空间作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。
本质上,命名空间就是定义了一个范围。
命名空间的定义和调用
- 定义:
namespace namespace_name {
// 代码声明
}
- 调用命名空间中的变量或函数:
-
namespacename
::func()/namespacename
::变量
-
- 使用using namespace
namespacename
提前声明命名空间,那么后续代码中使用命名空间的元素可以不加前缀
- 使用using namespace
-
- 即使当前文件中定义了命名空间,但没有使用using namespace
namespacename
提前声明命名空间,那么后续代码中使用命名空间的元素必须加前缀
- 即使当前文件中定义了命名空间,但没有使用using namespace
name::code; // code 可以是变量或函数
示例:
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
int main ()
{
// 调用第一个命名空间中的函数
first_space::func();
// 调用第二个命名空间中的函数
second_space::func();
return 0;
}
using 指令
使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。
这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。
可以使用多个命名空间。
示例:
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
using namespace first_space;
int main ()
{
// 调用第一个命名空间中的函数
func();
return 0;
}
- using指令也可以使用命名空间的特定函数或变量
示例:
using std::cout;
使用了上边的这行代码,代码中可以直接使用cout函数。但std库中的其他函数和变量仍然需要使用std::
前缀
不连续的命名空间
下边这段代码可以是定义一个新的命名空间,也可以是为命名空间增加新的元素。
namespace namespace_name {
// 代码声明
}
嵌套的命名空间
namespace namespace_name1 {
// 代码声明
namespace namespace_name2 {
// 代码声明
}
}
全局命名空间
在 C++ 中,如果你没有显式地指定命名空间,代码将位于默认命名空间中。
默认命名空间是一个没有名字的全局命名空间,通常被称为全局命名空间。
如果全局命名空间和其他命名空间的函数/变量名重复,编译器优先使用全局命名空间的函数/变量。
如果文件 A 中使用了 using namespace std;
,而文件 B 包含了 A 的头文件但没有使用 using namespace std;
,那么文件 B 本身不会自动引入 std 命名空间。
一个烧脑示例
#include <iostream>
using namespace std;
namespace A
{
int a = 100;
namespace B //嵌套一个命名空间B
{
int a =20;
}
}
int a = 200;//定义一个全局变量
int main(int argc, char *argv[])
{
cout <<"A::a ="<< A::a << endl;
cout <<"A::B::a ="<<A::B::a << endl;
cout <<"a ="<<a << endl;
cout <<"::a ="<<::a << endl;
int a = 30;
cout <<"a ="<<a << endl;
cout <<"::a ="<<::a << endl;
return 0;
}
结果:
A::a =100
A::B::a =20
a =200 //全局变量a
::a =200
a =30 //局部变量a
::a =200
全局变量 a 表达为 ::a,用于当有同名的局部变量时来区别两者。
模板
函数模板
template <typename type> ret-type func-name(parameter list)
{
// 函数的主体
}
#include <iostream>
#include <string>
using namespace std;
template <typename T>
inline T const& Max (T const& a, T const& b)
{
return a < b ? b:a;
}
int main ()
{
int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;
double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;
string s1 = "Hello";
string s2 = "World";
cout << "Max(s1, s2): " << Max(s1, s2) << endl;
return 0;
}
类模板
template <class type> class class-name {
.
.
.
}
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真。
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem)
{
// 追加传入元素的副本
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop ()
{
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
// 删除最后一个元素
elems.pop_back();
}
template <class T>
T Stack<T>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素的副本
return elems.back();
}
int main()
{
try {
Stack<int> intStack; // int 类型的栈
Stack<string> stringStack; // string 类型的栈
// 操作 int 类型的栈
intStack.push(7);
cout << intStack.top() <<endl;
// 操作 string 类型的栈
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const& ex) {
cerr << "Exception: " << ex.what() <<endl;
return -1;
}
}
预处理器
- 预处理器是一些指令,指示编译器在实际编译之前先完成预处理。
- 所有的预处理器指令都是以井号(#)开头
- 预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。
define预处理
宏
#define 预处理指令用于创建符号常量。该符号常量通常称为宏。
宏是一种简单的文本替换机制。
#define macro-name replacement-text
例如
#define PI 3.14159
当这define出现在一个文件中时,在该文件中后续出现的所有宏都将会在程序编译之前被替换为 replacement-text。
参数宏
可以使用 #define 来定义一个带有参数的宏
#include <iostream>
using namespace std;
#define MIN(a,b) (a<b ? a : b)
int main ()
{
int i, j;
i = 100;
j = 30;
cout <<"较小的值为:" << MIN(i, j) << endl;
return 0;
}
条件宏通常用于一些简单的文本替换和条件编译的场景,而函数更适用于定义和执行复杂的代码逻辑。在现代 C++ 中,推荐使用函数而不是宏,因为函数更具有可读性、可维护性,并且避免了宏的一些潜在问题。
条件编译
如果在指令 #ifdef DEBUG 之前已经定义了符号常量 DEBUG,则会对程序中的 cerr 语句进行编译。
#ifdef DEBUG
cerr <<"Variable x = " << x << endl;
#endif
可以使用 #if 0 语句注释掉程序的一部分
#if 0
不进行编译的代码
#endif
示例:
#include <iostream>
using namespace std;
#define DEBUG
#define MIN(a,b) (((a)<(b)) ? a : b)
int main ()
{
int i, j;
i = 100;
j = 30;
#ifdef DEBUG
cerr <<"Trace: Inside main function" << endl;
#endif
#if 0
/* 这是注释部分 */
cout << MKSTR(HELLO C++) << endl;
#endif
cout <<"The minimum is " << MIN(i, j) << endl;
#ifdef DEBUG
cerr <<"Trace: Coming out of main function" << endl;
#endif
return 0;
}
Trace: Inside main function
The minimum is 30
Trace: Coming out of main function
#和##运算符
#运算符会把 replacement-text 令牌转换为用引号引起来的字符串。
两个示例的结果都是”输出字符串“
#define func(c) #c
cout<< func(输出字符串);
##运算符用于连接两个令牌。
#define func(b,c) b##c
string bc = "输出字符串";
cout<< func(b,c);
预定义宏
#include <iostream>
using namespace std;
int main ()
{
cout << "Value of __LINE__ : " << __LINE__ << endl;
cout << "Value of __FILE__ : " << __FILE__ << endl;
cout << "Value of __DATE__ : " << __DATE__ << endl;
cout << "Value of __TIME__ : " << __TIME__ << endl;
return 0;
}
信号
捕捉信号 signal函数
signal(registered signal, signal handler)
这个函数接收两个参数:第一个参数是要设置的信号的标识符,第二个参数是指向信号处理函数的指针。
函数返回值是一个指向先前信号处理函数的指针。如果先前没有设置信号处理函数,则返回值为 SIG_DFL。如果先前设置的信号处理函数为 SIG_IGN,则返回值为 SIG_IGN。
示例:捕捉SIGINT
信号
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
void signalHandler( int signum )
{
cout << "Interrupt signal (" << signum << ") received.\n";
// 清理并关闭
// 终止程序
exit(signum);
}
int main ()
{
// 注册信号 SIGINT 和信号处理程序
signal(SIGINT, signalHandler);
while(1){
cout << "Going to sleep...." << endl;
sleep(1);
}
return 0;
}
当按下ctrl+c键 :
Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.
生成信号
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
int raise (signal sig);
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;
}
sleep 挂起
#include <windows.h>
sleep(100);//挂起100ms
linux:
#include <unistd.h>
sleep(10);//挂起10s
STL
C++ STL(标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。
C++ 标准模板库的核心包括以下三个组件:
标准库
https://www.runoob.com/cplusplus/cpp-standard-library.html