我不会C++,没问题,跟凯哥一起学(七)

前言

慢慢的步入有趣的地方,我们就会恍然大悟我不会C++,没问题,跟凯哥一起学(六),接下来让我们多叹息几次,我们看了下面的内容就会明白很多基础信息。

预处理器

预处理器是指令,在实际编译开始之前,预处理器会给编译器指令来预处理需要编译的信息。 所有的预处理指令以 # 开头,可能在预处理指令行之前出现的只有空白字符。预处理指令是不是 C++ 语句,所
以它们不会以分号( ; )结束。
你已经在所有的例子中都看到了 #include 指令。这个宏用于将头文件包含到源文件。
还有许多 C++ 支持的预处理指令,如 #include , #define , #if , #else , #line 等等。下面我们来看一 些重要的指令:

define 处理器

处理指令用来创建符号常量。这个符号常量被称为宏。指令的一般形式是:

#define macro-name replacement-text

当这一行出现在一个文件中时,在程序被编译之前,该文件中的所有后续出现的 macro-name 将会被 replacemen t-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; }

函数宏

定义一个带有如下参数的宏

#include <iostream>
using namespace std;
#define MIN(a,b) (((a)<(b)) ? a : b)
int main () {
   int i, j;
   i = 100;
   j = 30;
   cout <<"The minimum is " << MIN(i, j) << endl;
return 0; }
如果我们编译并运行上述代码,将会产生以下结果:
The minimum is 30

条件编译

有几种不同的指令,其可用于有选择的编译程序源代码的一部分。这个过程被称为条件编译。 条件预处理器结构很像 if 选择结构。思考下面的预处理代码:

#ifndef NULL
   #define NULL 0
#endif

你可以编译一个用于调试的程序,并且可以用单个宏打开或关闭调试开关,如下所示:

#ifdef DEBUG
   cerr <<"Variable x = " << x << endl;
#endif

如果符号常量 DEBUG 定义在 #ifdef DEBUG 指令之前,那么程序中的 cerr 语句会被编译。你可以使用 #if 0 语 句注释掉程序的一部分,如下所示:

   code prevented from compiling
#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
   /* This is commented part */
   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 中 # 和 ## 预处理器操作符是可用的。# 操作符会将要替代的文本符号转换成用双引号引起来的字符串。 思考下面的宏定义:

#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++ 提供了许多预定义的宏,如下所示:

宏:

1️⃣ __ LINE __:编译过后,其包含了当前程序行在程序内的行号
2️⃣ __ FILE __ : 编译过后,其包含了当前程序的程序名
3️⃣ __ DATE __ :其包含了由源文件转换为目标代码的日期,该日期是格式为 月/日/年 的字符串文本
4️⃣ __ TIME __ : 其包含了源文件编译的时间,该时间是 时:分:秒 形式的字符串文本
我们来看一个展示上面宏的例子:

#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

信号处理

信号是由操作系统传递到进程的中断,它可以提前终止一个程序。在 UNIX,LINUX,Mac OS X 或 Windows 系统 上,你可以通过按 Ctrl+C 产生一个中断。
有的信号不能被程序捕获到,但是下面列出的信号,你可以在程序中捕捉它们,并且可以基于这些信号进行相应 的操作。这些信号定义在 C++ 头文件 <csignal> 中。

信号

SIGABRT : 程序的异常终止,例如调用 abort
SIGFPE : 一个错误的算术运算,例如除以零或运算结果溢出。
SIGILL : 检测到非法指令。
SIGINT : 接收到交互注意信号。
SIGSEGV : 一个非法的存储访问。
SIGTERM : 发送给程序的终止请求信号。

signal() 函数

C++ 信号处理库提供 signal 函数来捕获意外事件。以下是 signal() 函数的语法:

void (*signal (int sig, void (*func)(int)))(int);

简单来说,这个函数接收两个参数:第一个参数是一个整数,表示信号号码;第二个参数是一个指向信号处理函
数的指针。
让我们用 signal() 函数写一个简单的 C++ 程序,用它来捕捉 SIGINT 信号。不管你想在程序中捕获什么信 号,你必须使用 signal 函数注册该信号,并将其与信号处理程序相关联。 例子如下所示:

#include <iostream>
#include <csignal>
using namespace std;
void signalHandler( int signum )
{
cout << "Interrupt signal (" << signum << ") received.\n";
// cleanup and close up stuff here
// terminate program
exit(signum);
}
int main ()
{
// register signal SIGINT and signal handler
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,SIGHU P。以下是我们使用 raise() 函数从程序内部发出一个信号的例子:

#include <iostream>
#include <csignal>
using namespace std;
void signalHandler( int signum )
{
cout << "Interrupt signal (" << signum << ") received.\n";
// cleanup and close up stuff here
// terminate program
   exit(signum);
}
int main ()
{
int i = 0;
// register signal SIGINT and signal handler
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 线程,或称 Pthreads,它提供了在许多类 Unix 的 POSIX 系统(如 FreeBSD,NetBSD,GNU/Linux,Mac OS X 和 Solari s)中可用的 API。

创建线程

我们使用下面的函数来创建一个 POSIX 线程:

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)

这里的 pthread_create 创建了一个新线程,并使其可执行。这个函数可以在代码中的任意位置调用任意次。 下面是详细的参数说明:

参数 : 描述

thread : 新线程的不透明、唯一的标识符,它由子函数返回。
attr : 一个不透明的属性对象,可用于设置线程属性。你可以指定一个线程的属性对象,默认值为 NULL。
start_routine : C++ 例程,线程一旦创建将会被执行。
arg : 一个传递给 start_routine 的参数。它必须传递一个 void 类型指针的引用。如果没有参数传 递,默认值为 NULL。
一个进程可创建的最大线程数是依赖实现决定的。线程一旦创建,它们之间是对等的,而且也有可能创建其它的线程。线程之间没有隐含的层次或依赖关系。

终止线程

我们使用下面的函数来终止一个 POSIX 线程:

#include <pthread.h>
pthread_exit (status)

此处的 pthread_exit 用于显式的退出一个线程。通常在线程已完成了其工作,并且没有存在的必要的时候,调 用 pthread_exit() 函数。
如果 main() 在其创建的线程之前终止,并且使用了 pthread_exit() 来退出线程,那么其线程将会继续执 行。否则,当 main() 终止后,这些线程将会自动终止。

例子

下面简单的样例代码,用 pthread_create() 函数创建了 5 个线程。每个线程均打印 “Hello World!”,然 后调用 pthread_exit() 函数终止了线程。

#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
void *PrintHello(void *threadid)
{
   long tid;
   tid = (long)threadid;
   cout << "Hello World! Thread ID, " << tid << endl;
   pthread_exit(NULL);
}
int main () {
   pthread_t threads[NUM_THREADS];
   int rc;
   int i;
   for( i=0; i < NUM_THREADS; i++ ){
cout << "main() : creating thread, " << i << endl;
rc = pthread_create(&threads[i], NULL,
  PrintHello, (void *)i);
  if (rc){
 cout << "Error:unable to create thread," << rc << endl;
 exit(-1);
} }
   pthread_exit(NULL);
}
使用 -lpthread 库编译上面的程序,如下所示:
$gcc test.cpp -lpthread
现在执行上面的程序,将会产生如下的结果:
main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Hello World! Thread ID, 0
Hello World! Thread ID, 1
Hello World! Thread ID, 2
Hello World! Thread ID, 3
Hello World! Thread 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 = "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);
}
当上述代码编译和执行后,将会有以下的结果:
 main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Thread ID : 3 Message : This is message
Thread ID : 2 Message : This is message
Thread ID : 0 Message : This is message
Thread ID : 1 Message : This is message
Thread ID : 4 Message : This is message

连接和分离线程

下面的两个函数,我们可以用它们来连接或分离线程:

pthread_join (threadid, status)
pthread_detach (threadid)

pthread_join() 子例程会阻塞调用它的线程,一直等到其指定的 threadid 的线程结束为止。当一个线程创建 后,它的属性决定了它是否是可连接的或可分离的。只有创建时属性为可连接的线程才可以连接。如果创建的是 一个可分离的线程,那么它永远不能连接。
下面的例子演示了如何使用 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;
   // Initialize and set thread 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, &amp;&amp;i );
  if (rc){
 cout << "Error:unable to create thread," << rc << endl;
 exit(-1);
} }
   // free attribute and wait for the other threads
   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 : 0 .... exiting
Sleeping in thread
Thread with id : 1 .... exiting
Sleeping in thread
Thread with id : 2 .... exiting
Sleeping in thread
Thread with id : 3 .... exiting
Sleeping in thread
Thread with id : 4 .... 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.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容