C++学习笔记(十一)-模板、泛型、信号、多线程

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 | 菜鸟教程

猜你喜欢

转载自blog.csdn.net/linux_huangyu/article/details/131974386