《C++多线程编程实战》——1.3 程序结构、执行流和运行时对象

简介:

本节书摘来自异步社区出版社《C++多线程编程实战》一书中的第1章,第1.3节,作者: 【黑山共和国】Milos Ljumovic(米洛斯 留莫维奇),更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.3 程序结构、执行流和运行时对象
编程范式是计算机编程的基本样式,主要有4种范式:命令式、声明式、函数式(或结构式)、面向对象式。C++是当今最流行的面向对象编程语言之一,集功能性、灵活性、实用性于一体。和C一样,程序员能很快地适应它。C++成功的关键在于,程序员可以根据实际需要做相应地调整。

但是,C++学起来并不轻松。有时,你会认为这是一门高深莫测、难以捉摸的语言,一门永远学不完也无法完全理解和掌握的语言。别担心,学习一门语言并不是要掌握它的所有细枝末节,关键要学会如何正确地用语言特性解决特定的问题。实践是最好的老师,根据具体情况尽可能多地使用相应的特性,有助于加深理解。

在给出示例前,我们先介绍一下查尔斯·西蒙尼的匈牙利表示法。他在1977年的博士论文中,使用元编程(Meta-Programming)(一种软件生产方法)在程序设计中制定了标准的表示法。文中规定类型或变量的第1个字母表示数据类型。例如,如果要给一个类命名,Test数据类型应该是CTest。第1个字母C表示Test是一个类。这个方法很不错,因为不熟悉Test数据类型的程序员会马上明白Test是一个类名。基本数据类型也可以这样处理,以int和double为例,iCount表示一个int类型的变量Count,而dValues表示一个double类型的变量Value。有了这些前缀,即使不熟悉代码也很容易识别它们的类型,提高了代码的可读性。

准备就绪
确定安装并运行了Visual Studio(VS)。

操作步骤
根据以下步骤创建我们的第1个程序示例。

1.创建一个默认的C++控制台应用程序[1],命名为TestDemo

2.打开TestDemo.cpp

3.输入下面的代码:

#include "stdafx.h" 
#include <iostream>

using namespace std; 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
  cout << "Hello world" << endl; 
  return 0; 
}```
示例分析
不同的编程技术使得C++程序的结构多种多样。绝大多数程序都必须有``#include`或预处理指令。

`#inlude <iostream>`告诉编译器包含`iostream.`h头文件,该头文件中有许多函数原型。这也意味着函数实现以及相关的库都要放进可执行文件中。因此,如果要使用某个API或函数,就要包含相应的头文件,必要时还必须添加包含API或函数实现的输入库。另外,要注意<header>和"header"的区别。前者`(< >`)表示从解决方案配置的项目路径开始搜索,而后者(`""`)表示从与C++项目相关的当前文件夹开始搜索。

`using``命令指示编译器要使用std名称空间。名称空间中包含对象声明和函数实现,有非常重要的用途。在包含第三方库时,名称空间能最大程度地减少两个不同软件包中同名函数的歧义。

我们需要实现一个程序的入口点:`main`函数。前面提到过, ANSI签名用`main`, Unicode签名用`wmain`,编译器根据项目属性页的预处理器定义确定签名用`_tmain`。对于控制台应用程序,main函数有以下4种不同的原型:

int _tmain(int argc, TCHAR* argv[])
void _tmain(int argc, TCHAR* argv[])
int _tmain(void)
void _tmain(void)`
第1种原型有两个参数:argcargv。第1个参数argc(即,参数计数)表示第2个参数argv(即,参数值)中的参数个数。形参argv是一个字符串数组,其中的每个字符串都代表一个命令行参数。argv中的第1个字符串一定是当前程序的名称。第2种原型和第1种原型的参数类型、参数个数相同,但是返回类型不同。这说明main函数可能返回值,也可能不返回值。该值将被返回给操作系统。第3种原型没有参数,并返回一个整型值。第4种原型既没有参数也没有返回类型。看来,用第1种原型作为练习很不错。

函数体中的第1条语句使用了cout对象。cout是C++中标准输出流的名称。整条语句的意思是:把一系列字符(该例中是Hello world字符序列)插入标准输出流(通常对应的是屏幕)。

cout对象声明在std名称空间的iostream标准文件中。因此,要是用该对象必须包含相应的头文件,并且在_tmain函数前面先声明其所属的名称空间。

在我们使用的原型中(int _tmain(int, _TCHAR*)),_tmain返回一个整数。因此,必须在return关键字后面指定相应的int类型值,本例中是0。向操作系统返回值时,0通常表示执行成功。但是,具体的值由操作系统决定。

这个小程序非常简单。我们以此为例解释main`例程作为每个C++程序入口点的基本结构和用法。

单线程程序按顺序逐行执行。因此,如果把所有的代码都写成一个线程,这样的程序对用户并不友好。

如图1.1所示,应用程序要等用户输入数据后,才能重新获得控制权继续执行。为此,可以创建并发线程来处理用户的输入。这样,应用程序随时都能响应,不会在等待用户输入时毫无反应了。线程处理完自己的任务后,可以给应用程序发信号,告诉程序用户已完成相应操作。


dd480b2f931ea75cec1c57f1bf46eec6324a4166

图1.1 单线程程序按顺序逐行执行

更多讨论
每次我们要在主执行流中单独执行一个操作,都必须考虑使用一个单独的线程。最简单的例子是,实现一边计算一边在进度条上反映计算的进度。想在同一个线程中处理计算和更新进度条,可能行不通。因为如果一个线程既要进行计算又要更新UI,就不能充分地与操作系统绘画交互。因此,一般情况下我们总是把UI线程与其他工作线程分开。

来看下面的例子。假设我们创建了一个用于计算的函数(如,计算指定角度的正弦值或余弦值),我们要同步显示计算过程的进度:

void CalculateSomething(int iCount) 
{ 
  int iCounter = 0; 
  while (iCounter++ < iCount) 
  { 
    // 计算部分 
    // 更新进度条部分 
  } 
}```
由于while循环的每次迭代都忙于依次执行语句,操作系统没有所需的时间逐步更新用户接口(该例中,用户接口指进度条),因此用户见到的可能是空的进度条。待该函数返回后,才会出现已经完全被填满的进度条。出现这种情况的原因是在主线程中创建了进度条。我们应该单独用一个线程来执行CalculateSomething函数,然后在每次迭代中给主线程发信号来逐步更新进度条。前面提到过,线程在CPU中以极快的速度切换,在我们看来进度条的更新与计算进度同步进行。

总而言之,每次处理并行任务时,如果要等待用户输入或依赖外部(如,远程服务器的响应),就应该为类似的操作单独创建一个线程,这样我们的程序才不会挂起无响应。
相关文章
|
1月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
3月前
|
机器学习/深度学习 人工智能 API
如何在c++侧编译运行一个aclnn(AOL)算子?
CANN的AOL库提供了一系列高性能算子API,优化了昇腾AI处理器的调用流程。通过两段式接口设计,开发者可以高效地调用算子库API,实现模型创新与应用,提升开发效率和模型性能。示例中展示了如何使用`aclnnAdd`算子,包括环境初始化、算子调用及结果处理等步骤。
|
9天前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
9天前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
1月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
1月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
140 14
|
9天前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
1月前
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
|
1月前
|
存储 程序员 C语言
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
|
2月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
92 19