C++ 模板
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector 或 vector 。
您可以使用模板来定义函数和类,接下来让我们一起来看看如何使用。
函数模板
模板函数定义的一般形式如下所示:
template typename type> ret-type func-name(parameter list)
{
// 函数的主体
}
在这里,type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。
下面是函数模板的实例,返回两个数中的最大值:
实例
#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;
}
当上面的代码被编译和执行时,它会产生下列结果:
Max(i, j): 39 Max(f1, f2): 20.7 Max(s1, s2): World
类模板
正如我们定义函数模板一样,我们也可以定义类模板。泛型类声明的一般形式如下所示:
template <class type> class class-name { . . . }
在这里,type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。
下面的实例定义了类 Stack<>,并实现了泛型方法来对元素进行入栈出栈操作:
实例
#include iostream>
#include vector>
#include cstdlib>
#include string>
#include stdexcept>
using namespace std;
template class T>
class Stack {
private:
vectorT> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真。
return elems.empty();
}
};
template class T>
void StackT>::push (T const& elem)
{
// 追加传入元素的副本
elems.push_back(elem);
}
template class T>
void StackT>::pop ()
{
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
// 删除最后一个元素
elems.pop_back();
}
template class T>
T StackT>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素的副本
return elems.back();
}
int main()
{
try {
Stackint> intStack; // int 类型的栈
Stackstring> 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;
}
}
当上面的代码被编译和执行时,它会产生下列结果:
7 hello Exception: Stack<>::pop(): empty stack
C++ 预处理器
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。
我们已经看到,之前所有的实例中都有 #include 指令。这个宏用于把头文件包含到源文件中。
C++ 还支持很多预处理指令,比如 #include、#define、#if、#else、#line 等,让我们一起看看这些重要指令。
#define 预处理
#define 预处理指令用于创建符号常量。该符号常量通常称为宏,指令的一般形式是:
#define macro-name replacement-text
当这一行代码出现在一个文件中时,在该文件中后续出现的所有宏都将会在程序编译之前被替换为 replacement-text。例如:
#include iostream>
using namespace std;
#define PI 3.14159
int main ()
{
cout "Value of PI :" PI endl;
return 0;
}
现在,让我们测试这段代码,看看预处理的结果。假设源代码文件已经存在,接下来使用 -E 选项进行编译,并把结果重定向到 test.p。现在,如果您查看 test.p 文件,将会看到它已经包含大量的信息,而且在文件底部的值被改为如下:
$ gcc -E test.cpp > test.p ... int main () { cout << "Value of PI :" << 3.14159 << endl; return 0; }
参数宏
您可以使用 #define 来定义一个带有参数的宏,如下所示:
#include iostream>
using namespace std;
#define MIN(a,b) (ab ? a : b)
int main ()
{
int i, j;
i = 100;
j = 30;
cout "较小的值为:" MIN(i, j) endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
较小的值为:30
条件编译
有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。
条件预处理器的结构与 if 选择结构很像。请看下面这段预处理器的代码:
#ifdef NULL #define NULL 0 #endif
您可以只在调试时进行编译,调试开关可以使用一个宏来实现,如下所示:
#ifdef DEBUG cerr <<"Variable x = " << x << endl; #endif
如果在指令 #ifdef DEBUG 之前已经定义了符号常量 DEBUG,则会对程序中的 cerr 语句进行编译。您可以使用 #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
# 和 ## 运算符
# 和 ## 预处理运算符在 C++ 和 ANSI/ISO C 中都是可用的。# 运算符会把 replacement-text 令牌转换为用引号引起来的字符串。
请看下面的宏定义:
实例
#include iostream>
using namespace std;
#define MKSTR( x ) #x
int main ()
{
cout MKSTR(HELLO C++) endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
HELLO C++
让我们来看看它是如何工作的。不难理解,C++ 预处理器把下面这行:
cout << MKSTR(HELLO C++) << endl;
转换成了:
cout << "HELLO C++" << endl;
## 运算符用于连接两个令牌。下面是一个实例:
#define CONCAT( x, y ) x ## y
当 CONCAT 出现在程序中时,它的参数会被连接起来,并用来取代宏。例如,程序中 CONCAT(HELLO, C++) 会被替换为 "HELLO C++",如下面实例所示。
实例
#include iostream>
using namespace std;
#define concat(a, b) a ## b
int main()
{
int xy = 100;
cout concat(x, y);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
100
让我们来看看它是如何工作的。不难理解,C++ 预处理器把下面这行:
cout << concat(x, y);
转换成了:
cout << xy;
C++ 中的预定义宏
C++ 提供了下表所示的一些预定义宏:
宏 |
描述 |
__LINE__ |
这会在程序编译时包含当前行号。 |
__FILE__ |
这会在程序编译时包含当前文件名。 |
__DATE__ |
这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。 |
__TIME__ |
这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。 |
让我们看看上述这些宏的实例:
实例
#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;
}
当上面的代码被编译和执行时,它会产生下列结果:
Value of __LINE__ : 6 Value of __FILE__ : test.cpp Value of __DATE__ : Feb 28 2011 Value of __TIME__ : 18:52:48
C++ 信号处理
信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。
有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++ 头文件 中。
信号 |
描述 |
SIGABRT |
程序的异常终止,如调用 abort 。 |
SIGFPE |
错误的算术运算,比如除以零或导致溢出的操作。 |
SIGILL |
检测非法指令。 |
SIGINT |
程序终止(interrupt)信号。 |
SIGSEGV |
非法访问内存。 |
SIGTERM |
发送到程序的终止请求。 |
signal() 函数
C++ 信号处理库提供了 signal 函数,用来捕获突发事件。以下是 signal() 函数的语法:
void (*signal (int sig, void (*func)(int)))(int);
这个看起来有点费劲,以下语法格式更容易理解:
signal(registered signal, signal handler)
这个函数接收两个参数:第一个参数是要设置的信号的标识符,第二个参数是指向信号处理函数的指针。函数返回值是一个指向先前信号处理函数的指针。如果先前没有设置信号处理函数,则返回值为 SIG_DFL。如果先前设置的信号处理函数为 SIG_IGN,则返回值为 SIG_IGN。
让我们编写一个简单的 C++ 程序,使用 signal() 函数捕获 SIGINT 信号。不管您想在程序中捕获什么信号,您都必须使用 signal 函数来注册信号,并将其与信号处理程序相关联。看看下面的实例:
实例
#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;
}
当上面的代码被编译和执行时,它会产生下列结果:
Going to sleep.... Going to sleep.... Going to sleep....
现在,按 Ctrl+C 来中断程序,您会看到程序捕获信号,程序打印如下内容并退出:
Going to sleep.... Going to sleep.... Going to sleep.... Interrupt signal (2) received.
raise() 函数
您可以使用函数 raise() 生成信号,该函数带有一个整数信号编号作为参数,语法如下:
int raise (signal sig);
在这里,sig 是要发送的信号的编号,这些信号包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。以下是我们使用 raise() 函数内部生成信号的实例:
实例
#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;
}
当上面的代码被编译和执行时,它会产生下列结果,并会自动退出:
Going to sleep.... Going to sleep.... Going to sleep.... Interrupt signal (2) received.
C++ 多线程
多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下,两种类型的多任务处理:基于进程和基于线程。
- 基于进程的多任务处理是程序的并发执行。
- 基于线程的多任务处理是同一程序的片段的并发执行。
多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程,每个线程定义了一个单独的执行路径。
本教程假设您使用的是 Linux 操作系统,我们要使用 POSIX 编写多线程 C++ 程序。POSIX Threads 或 Pthreads 提供的 API 可在多种类 Unix POSIX 系统上可用,比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。
创建线程
下面的程序,我们可以用它来创建一个 POSIX 线程:
#include <pthread.h> pthread_create (thread, attr, start_routine, arg)
在这里,pthread_create 创建一个新的线程,并让它可执行。下面是关于参数的说明:
参数 |
描述 |
thread |
指向线程标识符指针。 |
attr |
一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。 |
start_routine |
线程运行函数起始地址,一旦线程被创建就会执行。 |
arg |
运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。 |
创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败。
终止线程
使用下面的程序,我们可以用它来终止一个 POSIX 线程:
#include <pthread.h> pthread_exit (status)
在这里,pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。
如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。
实例
以下简单的实例代码使用 pthread_create() 函数创建了 5 个线程,每个线程输出"Hello Runoob!":
实例
#include iostream>
// 必须的头文件
#include pthread.h>
using namespace std;
#define NUM_THREADS 5
// 线程的运行函数
void* say_hello(void* args)
{
cout "Hello Runoob!" endl;
return 0;
}
int main()
{
// 定义线程的 id 变量,多个变量使用数组
pthread_t tids[NUM_THREADS];
for(int i = 0; i < NUM_THREADS; ++i)
{
//参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
if (ret != 0)
{
cout "pthread_create error: error_code=" ret endl;
}
}
//等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
pthread_exit(NULL);
}
使用 -lpthread 库编译下面的程序:
$ g++ test.cpp -lpthread -o test.o
现在,执行程序,将产生下列结果:
$ ./test.o Hello Runoob! Hello Runoob! Hello Runoob! Hello Runoob! Hello Runoob!
以下简单的实例代码使用 pthread_create() 函数创建了 5 个线程,并接收传入的参数。每个线程打印一个 "Hello Runoob!" 消息,并输出接收的参数,然后调用 pthread_exit() 终止线程。
实例
//文件名:test.cpp
#include iostream>
#include cstdlib>
#include pthread.h>
using namespace std;
#define NUM_THREADS 5
void *PrintHello(void *threadid)
{
// 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
int tid = *((int*)threadid);
cout "Hello Runoob! 线程 ID, " tid endl;
pthread_exit(NULL);
}
int main ()
{
pthread_t threads[NUM_THREADS];
int indexes[NUM_THREADS];// 用数组来保存i的值
int rc;
int i;
for( i=0; i < NUM_THREADS; i++ ){
cout "main() : 创建线程, " i endl;
indexes[i] = i; //先保存i的值
// 传入的时候必须强制转换为void* 类型,即无类型指针
rc = pthread_create(&threads[i], NULL,
PrintHello, (void *)&(indexes[i]));
if (rc){
cout "Error:无法创建线程," rc endl;
exit(-1);
}
}
pthread_exit(NULL);
}
现在编译并执行程序,将产生下列结果:
$ g++ test.cpp -lpthread -o test.o $ ./test.o main() : 创建线程, 0 main() : 创建线程, 1 Hello Runoob! 线程 ID, 0 main() : 创建线程, Hello Runoob! 线程 ID, 21 main() : 创建线程, 3 Hello Runoob! 线程 ID, 2 main() : 创建线程, 4 Hello Runoob! 线程 ID, 3 Hello Runoob! 线程 ID, 4
向线程传递参数
这个实例演示了如何通过结构传递多个参数。您可以在线程回调中传递任意的数据类型,因为它指向 void,如下面的实例所示:
实例
#include iostream>
#include cstdlib>
#include pthread.h>
using namespace std;
#define NUM_THREADS 5
struct thread_data{
int thread_id;
char *message;
};
void *PrintHello(void *threadarg)
{
struct thread_data *my_data;
my_data = (struct thread_data *) threadarg;
cout "Thread ID : " my_data->thread_id ;
cout " Message : " my_data->message endl;
pthread_exit(NULL);
}
int main ()
{
pthread_t threads[NUM_THREADS];
struct thread_data td[NUM_THREADS];
int rc;
int i;
for( i=0; i < NUM_THREADS; i++ ){
cout "main() : creating thread, " i endl;
td[i].thread_id = i;
td[i].message = (char*)"This is message";
rc = pthread_create(&threads[i], NULL,
PrintHello, (void *)&td[i]);
if (rc){
cout "Error:unable to create thread," rc endl;
exit(-1);
}
}
pthread_exit(NULL);
}
当上面的代码被编译和执行时,它会产生下列结果:
$ g++ -Wno-write-strings test.cpp -lpthread -o test.o $ ./test.o main() : creating thread, 0 main() : creating thread, 1 Thread ID : 0 Message : This is message main() : creating thread, Thread ID : 21 Message : This is message main() : creating thread, 3 Thread ID : 2 Message : This is message main() : creating thread, 4 Thread ID : 3 Message : This is message Thread ID : 4 Message : This is message
连接和分离线程
我们可以使用以下两个函数来连接或分离线程:
pthread_join (threadid, status) pthread_detach (threadid)
pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止。当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连接。
这个实例演示了如何使用 pthread_join() 函数来等待线程的完成。
实例
#include iostream>
#include cstdlib>
#include pthread.h>
#include unistd.h>
using namespace std;
#define NUM_THREADS 5
void *wait(void *t)
{
int i;
long tid;
tid = (long)t;
sleep(1);
cout "Sleeping in thread " endl;
cout "Thread with id : " tid " ...exiting " endl;
pthread_exit(NULL);
}
int main ()
{
int rc;
int i;
pthread_t threads[NUM_THREADS];
pthread_attr_t attr;
void *status;
// 初始化并设置线程为可连接的(joinable)
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for( i=0; i < NUM_THREADS; i++ ){
cout "main() : creating thread, " i endl;
rc = pthread_create(&threads[i], NULL, wait, (void *)&i );
if (rc){
cout "Error:unable to create thread," rc endl;
exit(-1);
}
}
// 删除属性,并等待其他线程
pthread_attr_destroy(&attr);
for( i=0; i < NUM_THREADS; i++ ){
rc = pthread_join(threads[i], &status);
if (rc){
cout "Error:unable to join," rc endl;
exit(-1);
}
cout "Main: completed thread id :" i ;
cout " exiting with status :" status endl;
}
cout "Main: program exiting." endl;
pthread_exit(NULL);
}
当上面的代码被编译和执行时,它会产生下列结果:
main() : creating thread, 0 main() : creating thread, 1 main() : creating thread, 2 main() : creating thread, 3 main() : creating thread, 4 Sleeping in thread Thread with id : 4 ...exiting Sleeping in thread Thread with id : 3 ...exiting Sleeping in thread Thread with id : 2 ...exiting Sleeping in thread Thread with id : 1 ...exiting Sleeping in thread Thread with id : 0 ...exiting Main: completed thread id :0 exiting with status :0 Main: completed thread id :1 exiting with status :0 Main: completed thread id :2 exiting with status :0 Main: completed thread id :3 exiting with status :0 Main: completed thread id :4 exiting with status :0 Main: program exiting.
std::thread
C++ 11 之后添加了新的标准线程库 std::thread,std::thread 在 头文件中声明,因此使用 std::thread 时需要包含 在 头文件。
之前一些编译器使用 C++ 11 的编译参数是 -std=c++11:
g++ -std=c++11 test.cpp
std::thread 默认构造函数,创建一个空的 std::thread 执行对象。
#include<thread> std::thread thread_object(callable)
一个可调用对象可以是以下三个中的任何一个:
- 函数指针
- 函数对象
- lambda 表达式
定义 callable 后,将其传递给 std::thread 构造函数 thread_object。
实例// 演示多线程的CPP程序
// 使用三个不同的可调用对象
#include
#include
using namespace std;
// 一个虚拟函数
void foo(int Z)
{
for (int i = 0; i Z; i++) {
cout "线程使用函数指针作为可调用参数\n";
}
}
// 可调用对象
class thread_obj {
public:
void operator()(int x)
{
for (int i = 0; i x; i++)
cout "线程使用函数对象作为可调用参数\n";
}
};
int main()
{
cout "线程 1 、2 、3 "
"独立运行" endl;
// 函数指针
thread th1(foo, 3);
// 函数对象
thread th2(thread_obj(), 3);
// 定义 Lambda 表达式
auto f = [](int x) {
for (int i = 0; i x; i++)
cout "线程使用 lambda 表达式作为可调用参数\n";
};
// 线程通过使用 lambda 表达式作为可调用的参数
thread th3(f, 3);
// 等待线程完成
// 等待线程 t1 完成
th1.join();
// 等待线程 t2 完成
th2.join();
// 等待线程 t3 完成
th3.join();
return 0;
}
使用 C++ 11 的编译参数 -std=c++11:
g++ -std=c++11 test.cpp
当上面的代码被编译和执行时,它会产生下列结果:
线程 1 、2 、3 独立运行 线程使用函数指针作为可调用参数 线程使用函数指针作为可调用参数 线程使用函数指针作为可调用参数 线程使用函数对象作为可调用参数 线程使用函数对象作为可调用参数 线程使用函数对象作为可调用参数 线程使用 lambda 表达式作为可调用参数 线程使用 lambda 表达式作为可调用参数 线程使用 lambda 表达式作为可调用参数
更多实例参考:C++ 多线程: http://www.runoob.com/w3cnote/cpp-multithread-demo.htmlC++ std::thread: C++ std::thread | 菜鸟教程