C++入门笔记:高级编程
文件和流
-
打开文件
void open (const char *filename, ios::openmode mode);
- ios::app 追加模式。所有写入都追加到文件末尾
- ios::ate 文件打开后定位到文件末尾
- ios::in 打开文件用于读取
- ios::out 打开文件用于写入
- ios::trunc 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。
-
关闭文件
void close();
-
写入文件
- 使用流插入运算符 ( << ),向 ofstream / fstream 流中写入信息
-
读取文件
- 使用流提取运算符 ( >> ),向 ifstream / fstream 流中写入信息
-
文件读写实例
#include
#include 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; } -
文件位置指针
-
stream 和 ostream 都提供了用于重新定位文件位置指针的成员函数。
- istream 的 seekg ("seek & get")
- ostream 的 seekp ("seek & put")
// 定位到 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 );
-
异常处理
-
try / catch / throw
- try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
- catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
- throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
-
C++ 标准的异常
异常 描述 std::exception 该异常是所有标准 C++ 异常的父类。 std::bad_alloc 该异常可以通过 new 抛出。 std::bad_cast 该异常可以通过 dynamic_cast 抛出。 std::bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用。 std::bad_typeid 该异常可以通过 typeid 抛出。 std::logic_error 理论上可以通过读取代码来检测到的异常。 std::domain_error 当使用了一个无效的数学域时,会抛出该异常。 std::invalid_argument 当使用了无效的参数时,会抛出该异常。 std::length_error 当创建了太长的 std::string 时,会抛出该异常。 std::out_of_range 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator。 std::runtime_error 理论上不可以通过读取代码来检测到的异常。 std::overflow_error 当发生数学上溢时,会抛出该异常。 std::range_error 当尝试存储超出范围的值时,会抛出该异常。 std::underflow_error 当发生数学下溢时,会抛出该异常。 -
定义新的异常
- 通过继承或重载 exception 类来定义新的异常
#include
#include using namespace std; struct MyException : public exception { 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) { //其他的错误 } }
- 通过继承或重载 exception 类来定义新的异常
动态内存
-
C++ 程序中的内存分为两个部分
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
可以使用 new 运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。
不需要动态分配内存时,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。
-
new 和 delete 运算符
-
使用 new 运算符来为任意的数据类型动态分配内存
// new data-type; // 如果自由存储区已被用完,可能无法成功分配内存。所以建议检查 new 运算符是否返回 NULL 指针。 double* pvalue = NULL; if( !(pvalue = new double )) { cout << "Error: out of memory." <
-
使用 delete 操作符释放它所占用的内存
#include
using namespace std; int main () { double* pvalue = NULL; // 初始化为 null 的指针 pvalue = new double; // 为变量请求内存 *pvalue = 29494.99; // 在分配的地址存储值 cout << "Value of pvalue : " << *pvalue << endl; delete pvalue; // 释放内存 return 0; }
-
-
数组的动态内存分配
int ROW = 2; int COL = 3; double **pvalue = new double* [ROW]; // 为行分配内存 // 为列分配内存 for(int i = 0; i < COL; i++) { pvalue[i] = new double[COL]; } for(int i = 0; i < COL; i++) { delete[] pvalue[i]; } delete [] pvalue;
-
对象的动态内存分配
#include
using namespace std; class Box { public: Box() { cout << "调用构造函数!" <
命名空间
命名空间可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。
-
定义命名空间
#include
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 指令
#include
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; } #include
using std::cout; int main () { cout << "std::endl is used with std!" << std::endl; return 0; } -
不连续的命名空间
- 命名空间可以定义在几个不同的部分中,因此命名空间是由几个单独定义的部分组成的。一个命名空间的各个组成部分可以分散在多个文件中。
-
嵌套的命名空间
#include
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::second_space; int main () { // 调用第二个命名空间中的函数 func(); return 0; }
模板
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector
或 vector 。 -
函数模板
template
ret-type func-name(parameter list) { // 函数的主体 } - 在这里,type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。
#include
#include using namespace std; template 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 class-name { } - 在这里,type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。
#include
#include #include #include #include using namespace std; template class Stack { private: vector elems; // 元素 public: void push(T const&); // 入栈 void pop(); // 出栈 T top() const; // 返回栈顶元素 bool empty() const{ // 如果为空则返回真。 return elems.empty(); } }; template void Stack ::push (T const& elem) { // 追加传入元素的副本 elems.push_back(elem); } template void Stack ::pop () { if (elems.empty()) { throw out_of_range("Stack<>::pop(): empty stack"); } // 删除最后一个元素 elems.pop_back(); } template T Stack ::top () const { if (elems.empty()) { throw out_of_range("Stack<>::top(): empty stack"); } // 返回最后一个元素的副本 return elems.back(); } int main() { try { Stack intStack; // int 类型的栈 Stack stringStack; // string 类型的栈 // 操作 int 类型的栈 intStack.push(7); cout << intStack.top() <
预处理器
-
#define 预处理
-
define 预处理指令用于创建符号常量。该符号常量通常称为宏。
#define macro-name replacement-text
-
-
函数宏
-
使用 #define 来定义一个带有参数的宏:
#define MIN(a,b) (((a)<(b)) ? a : b)
-
-
条件编译
有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。
-
ifdef & endif
#ifndef NULL #define NULL 0 #endif
-
DEBUG 模式
#ifdef DEBUG cerr <<"Variable x = " << x << endl; #endif
-
可以使用 #if 0 语句注释掉程序的一部分。
#if 0 不进行编译的代码 #endif
-
# 和 ## 运算符
-
运算符会把 replacement-text 令牌转换为用引号引起来的字符串。
#include
using namespace std; #define MKSTR( x ) #x int main () { cout << MKSTR(HELLO C++) << endl; return 0; } -
运算符用于连接两个令牌。
#include
using namespace std; #define concat(a, b) a ## b int main() { int xy = 100; cout << concat(x, y); return 0; }
-
-
预定义宏
宏 描述 _LINE_ 这会在程序编译时包含当前行号。 _FILE_ 这会在程序编译时包含当前文件名。 _DATE_ 这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。 _TIME_ 这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。
信号处理
信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。
-
可捕获的信号表(定义在
中) 信号 描述 SIGABRT 程序的异常终止,如调用 abort。 SIGFPE 错误的算术运算,比如除以零或导致溢出的操作。 SIGILL 检测非法指令。 SIGINT 接收到交互注意信号。 SIGSEGV 非法访问内存。 SIGTERM 发送到程序的终止请求。 -
signal() 函数
-
用来捕获突发事件
void (*signal (int sig, void (*func)(int))(int);
接收两个参数:第一个参数是一个整数,代表信号编号;第二个参数是一个指向信号处理函数的指针。
-
无论要在程序中捕获什么信号,都必须使用 singal 函数来注册信号,并将其与信号处理程序相关联。
#include
#include 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; }
-
-
raise() 函数
-
使用函数 raise() 生成信号。
int raise (signal sig);
-
sig 是要发送的信号编号,这些信号包括:SIGINT SIGABRT SIGFPE SIGILL SIGSEGV SIGTERM SIGHUP
#include
#include 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; }
-