从零开始:Qt可视化程序设计基础教程

978-7-115-57372-8
作者: 彭凌西唐春明陈统
译者:
编辑: 张天怡

图书目录:

详情

本书主要介绍C++的基础知识和Qt编程的相关知识,帮助读者尽快掌握Qt编程的相关技术。 本书第1~4章主要介绍C++基础语法、类和对象、继承与派生、虚函数与多态等Qt编程常用的C++内容,让读者快速掌握Qt编程的基础知识。第5~9章主要介绍Qt编程的相关内容,帮助读者快速入门,并通过多个实例让读者进一步掌握Qt编程的相关应用。 本书不仅适合相关专业的学生参考使用,也适合对Qt编程感兴趣的读者阅读。

图书摘要

版权信息

书名:从零开始Qt可视化程序设计基础教程

ISBN:978-7-115-57372-8

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。


版  权

编  著 彭凌西 唐春明 陈 统

责任编辑 张天怡

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315

内容提要

本书主要介绍C++的基础知识和Qt编程的相关知识,帮助读者尽快掌握Qt编程的相关技术。

本书第1~4章主要介绍C++基础语法、类和对象、继承与派生、虚函数与多态等Qt编程常用的C++内容,让读者快速掌握Qt编程的基础知识。第5~9章主要介绍Qt编程的相关内容,帮助读者快速入门,并通过多个实例让读者进一步掌握Qt编程的相关应用。

本书不仅适合相关专业的学生参考使用,也适合对Qt编程感兴趣的读者阅读。

“可视化程序设计”是理工科极为重要的一门专业课程,主要介绍类、对象、属性、方法、事件等现代程序设计的先进理念和思想。通过该课程,学生可熟练掌握面向对象的程序设计方法,提高可视化界面的应用程序设计技术。

国务院在2017年7月印发了《新一代人工智能发展规划》,明确提出实施全民智能教育项目,逐步推广编程教育。C++语言功能强大、灵活,适用于各种编程需求,但C++语言众多的概念让初学者望而却步。Qt编程目前已被广泛应用于嵌入式系统、电力系统和军工系统等与硬件交互的界面系统中,是可视化程序设计(又称可视化编程)的主要选择。目前,能做到简明扼要、通俗易懂地介绍C++语言基础,并与可视化程序设计工具Qt结合,让读者高效、快速掌握人工智能编程的图书和资料甚少。

本书编者结合在高校与企业多年的研究和教学经验,深入浅出地介绍C++语言基础、类和对象,然后对Qt编程中的窗口、菜单、绑定信号以及槽等概念操作过程进行详细的介绍与分析,并给出全部已编译运行的源代码及配套课件等丰富的资源,读者可通过学习本书事半功倍地掌握C++语言和Qt编程,达到能尽快编写人工智能程序的目的。

相信书的出版会对想尽快掌握C++语言和Qt编程的读者与研究人员大有裨益,从而促进编程教育和人工智能的发展。同时也希望有更多的研究人员能够掌握可视化程序设计技术,并从事人工智能研究和教育工作,为推动我国新一代人工智能创新活动的蓬勃发展做出自己的贡献。

教授 中国科学院院士

2021年10月

前  言

“可视化程序设计”是理工科极为重要的一门专业课程,实践性很强。其教学目标是使学生掌握可视化程序设计的基本方法、编程技能并具备上机调试能力,熟悉界面设计,掌握各种常用类(有些开发工具称控件,实际也是类)的属性和方法,培养学生应用计算机编程解决实际问题的能力,为今后实际工作中进行大型工程应用软件的设计与开发打下坚实的基础。

可视化程序设计以“所见即所得”为原则,力图实现编程工作的可视化。C++语言属于编程语言中的“王者”,Qt是可视化程序设计的重要框架,是机器视觉领域的重要工具。但是,目前将C++语言和Qt编程结合,介绍可视化程序设计的图书较少。与已有的可视化程序设计图书相比,本书具有以下特色。

通俗易懂,深入浅出。本书通过大量编程实例的程序演示、代码注释讲解及运行结果分析,语言简洁、精练、通俗易懂地介绍C++语言基础、类、对象、继承以及多态等难以掌握的概念。本书初稿经过没有编程基础的学生试读,多名教师试用,历时3年,通过反复修改,直到易懂、易教为止,可谓“数年磨一剑”。

重点突出,循序渐进。本书针对C++语言提供多个编程实例,但不追求全面和系统,只重点介绍C++语言基础的核心和面向对象思想的精华,以求让读者尽快掌握Qt编程技术。待读者掌握面向对象的基本思想后,可继续深入学习类模板、运算符重载、向量等内容。

实例丰富,快速上手。本书针对Qt编程提供多个程序实例,如简易计算器、多线程、数据库、网络应用编程、文件操作、基于人脸检测的多路入侵监视系统等多个应用方向,部分实例是研发实例的精简。这些实例没有一味追求实用性和全面性,尽量只讲解基本原理和操作,并添加详尽的代码注释,以便读者快速掌握。但这些程序实例具有可维护性和扩充性,可以快速扩展应用到实践中。

资源丰富,易学易教。本书提供在Qt 6.0编程环境中编译通过的全部示例源代码、配套课件等立体式全方位资源,读者可在QQ群(764353211)共享文件夹中获取。

如果读者没有学习过任何编程语言,或仅有C语言基础,建议从第1章开始学习;如果读者已学习过C++语言,只想学可视化程序设计技术或Qt编程,则可在学习1.2节和1.3.1小节后,直接进入第5章的学习。本书最后提供包含Qt编程常见问题的附录,建议读者阅读。

读者如果有任何意见和反馈,请联系我们(关喜荣:836030680@qq.com;彭凌西:flyingday@139. com)。

本书第1章、第5~6章由梁志炜完成,第2~4章、第8章由关喜荣完成,第7章由彭凌西完成,第9章由唐春明完成,附录由陈统完成。在编写过程中,本书还得到了很多专家、企业人员以及师生友人的大力支持和帮助。肖忠、彭邵湖、林煜桐、郭俊婷、谢翔、黄明龙等众多老师和学生对全书进行了试读与校稿,并提出了许多宝贵的意见,让本书不仅通俗易懂,而且讲解明晰。他们认真、细致的工作让我感动。本书还得到了数据恢复四川省重点实验室、广州大学研究生院和教务处教材出版基金的大力支持,受到国家自然科学基金项目(12171114、 61772147和61100150)、广东省自然科学基金基础研究重大培育项目(2015A030308016)、国家密码管理局“十三五”国家密码发展基金项目(MMJJ20170117)、广州市教育局协同创新重大项目(1201610005)、密码科学技术国家重点实验室开放课题项目(MMKFKT201913)的资助,得到了统信软件技术有限公司、广东省机械研究所有限公司、广东轩辕网络科技股份有限公司和广州粤嵌通信科技股份有限公司等的竭诚帮助。

在本书编写过程中,我参考了互联网上众多的资料、代码、网络视频,以及其他图书,在此谨一并表示最诚挚的感谢!

感谢可爱的女儿们,你们的天真和烂漫让我的一切忧愁与烦恼烟消云散。

最后,与读者分享我在多年的计算机教学、研究过程中的体会:改变你的人生,从编程开始!

彭凌西

2021年12月

第1章 C++程序基础

C++是C语言的继承,是一种使用非常广泛的计算机编程语言。C++作为一种静态数据类型检查的、支持多重编程范式的通用程序设计语言,支持过程化程序设计、数据抽象化和面向对象程序设计、泛型程序设计、基于原则设计等多种程序设计风格。C++的编程领域甚广,它常用于系统开发、引擎开发等应用领域,深受广大程序员的喜爱。

本章将详细介绍C++的基础语法和基础使用。

本章主要内容和学习目标如下。

C++简介。

环境搭建。

C++基础语法。

基本数据类型和变量。

运算符。

控制台数据输入和输出。

结构化程序设计。

参数和函数。

数组与字符串。

指针。

结构体。

异常处理。

命名空间。

在统信UOS环境下安装Qt。

1.1 C++简介

C++属于编程语言中的“王者”,也是目前软件开发的主流语言之一。下面对C++进行简要说明。

· 1.1.1 C++语言简介

C++是一种面向对象的计算机程序设计语言,由美国电话电报公司(AT&T)贝尔实验室的本贾尼·斯特劳斯特卢普(Bjarne Stroustrup)博士在20世纪80年代初期发明并实现,最初这种语言被称作“C with Classes”(带类的C)。C++是C语言的继承,进一步扩充和完善了C语言,成为一种面向对象的程序设计语言。

· 1.1.2 C++与C语言的不同

C++与C语言的主要区别如下。

1.面向过程语言和面向对象语言

C语言是面向过程语言,而C++是面向对象语言。C语言和C++的区别,也就是面向过程和面向对象的区别。面向过程编程就是分析出解决问题的步骤(功能模块),然后把这些步骤一步一步地实现,使用的时候依次调用就可以了;面向对象编程就是把问题中的事和物抽象成各个类,然后建立对象,其目的不是完成一个步骤,而是描述对象在整个解决问题的步骤中的行为。下面,以玩五子棋游戏为例进行说明。

(1)用面向过程的思想来考虑:开始游戏,白子先走,绘制画面,判断输赢;轮到黑子,绘制画面,判断输赢;重复前面的过程,输出最终结果。

(2)用面向对象的思想来考虑:先设计棋子类、棋盘系统类、规定系统类以及输出系统类,然后构造具体对象,包括黑白双方(两者的行为是一样的)、棋盘系统(负责绘制画面)、规定系统(规定输赢、犯规等)、输出系统(输出赢家)。面向对象就是实物高度抽象化(功能划分),面向过程就是自顶向下的编程(步骤划分)。

2.具体语言的不同

(1)关键字(又称关键词)不同:C语言有32个关键字,而C++有63个关键字,一些关键字的细微区别如下。

① struct:在C语言中,结构体struct定义的变量中不能有函数;而在C++中可以有函数(或称为方法)。

② malloc:malloc()函数的返回值为void,在C语言中可以赋值给任意类型的指针,在C++中必须强制转换类型,否则会报错。

③ 结构体定义struct和类定义class:class是对struct的扩展,struct默认的访问权限是public,而class默认的访问权限是private。

(2)扩展名不同:C语言源文件扩展名为.c,C++源文件扩展名为.cpp。在Qt中,如果在创建源文件时什么都不给,扩展名默认是.cpp。

(3)返回值类型不同:在C语言中,如果一个函数没有指定返回值类型,默认返回int类型;在C++中,如果一个函数没有返回值,则必须指定为void。

1.2 环境搭建

Qt(发音为 [kju:t],音同 cute)是一个跨平台的C++开发框架,主要用来开发图形用户界面(Graphical User Interface,GUI)程序,当然也可以开发不带界面的命令行接口(Command Line Interface,CLI)程序。本书将使用Qt来开发C++程序。本书使用的Qt版本号为6.0,操作系统为Windows 10。

Qt从5.15版本开始(5.14.2是官方最后一个可离线下载安装包的版本),对非商业版本,也就是开源版本,不再提供已经制作好的离线安装包,此时用户只有两种选择:一种是编译源代码方式,该方式步骤烦琐,且需严格遵循步骤,一般要花费数小时;另一种是在线联网安装,在清华大学开源软件镜像站或Qt官网下载在线安装包qt-unified-windows-x86-online.exe,然后单击安装,如图1-1所示。如果是5.14及以前的版本,在线安装过程类似,安装过程本书不再介绍。

图1-1 安装包下载

在线安装下载速度慢,建议选择安装代理软件Fiddler 5,将下载地址重定向到清华大学的镜像站。具体操作:打开Fiddler,在页面左下方黑色的地方输入以下内容,并按“Enter”键,如图1-2所示。

urlreplace download.qt.io mirrors.tuna.tsinghua.edu.cn/qt

单击运行Qt安装包后,在Qt Open Source Usage Obligations页面中进行设置:如果是个人使用,勾选最底下的复选框;如果是公司使用,需要填写公司名称,如图1-3所示。然后单击“Next”进入下一步。

如图1-4所示,在Contribute to Qt Development页面中,选择第一个选项(发送统计信息帮助 Qt 改进)或者第二个选项(禁止发送)均可,单击“Next”进入下一步。

图1-2 代理软件及配置

图1-3 使用义务

图1-4 为Qt做贡献

在Installation Folder页面中选择“Custom installation”,并按照个人安装习惯选择常用路径,然后单击“Next”进入下一步,如图1-5所示。

图1-5 选择安装目录

在图1-6中,选择需要安装的组件,然后单击“Next”进入License Agreement页面,选择“Agree”,单击“Next”进入Start Menu shortcuts页面,单击“Next”进入Ready to Install页面,单击安装按钮,就可以进行安装了。安装过程比较长,注意耐心等待。

图1-6 选择Qt安装组件

安装成功后打开Qt Creator,运行界面如图1-7所示。

图1-7 Qt Creator运行界面

1.3 C++基础语法

C++的基础语法相当繁杂,而且还在不停地更新,这让很多初学者在编写代码的时候花费了很长时间。本书通过大量例子加上通俗易懂的详细讲解,可让读者深入浅出地学习C++。

· 1.3.1 第一个C++项目

下面用Qt开始执行第一个项目HelloWorld吧!

(1)单击“开始”菜单的Qt Creator,运行Qt Creator,单击欢迎界面Projects处的“New”,创建一个新项目(或通过“文件”菜单新建一个新的项目),如图1-8所示。

图1-8 创建项目第1步

(2)单击选择对话框左边的“Non-Qt Project”,然后单击选择“Plain C++ Application”作为模板建立项目,如图1-9所示。

图1-9 创建项目第2步

(3)自定义项目的名称和创建路径,如图1-10所示,输入项目名称“HelloWorld”。

图1-10 创建项目第3步

(4)选择默认编译系统为“qmake”,如图1-11所示,然后单击“下一步”按钮。

图1-11 创建项目第4步

(5)接下来两步都直接单击“下一步”按钮,如图1-12和图1-13所示。然后单击“完成”按钮,如图1-14所示。

图1-12 创建项目第5步

图1-13 创建项目第6步

图1-14 创建项目第7步

(6)在图1-15左侧单击“main.cpp”文件,并编写第一个项目HelloWorld,见例1-1。

图1-15 编写代码图

例1-1:HelloWorld项目。

    #include <iostream>
    using namespace std;
    // main() 是程序开始执行的地方
    int main()
    {
      cout << "Hello World"; // 输出 Hello World
      return 0;
    }

(7)例1-1运行结果如图1-16所示。

图1-16 例1-1运行结果

通过例1-1的代码,我们可以了解如何创建项目、编写代码和运行代码,接下来分析这一段代码。

C++定义了一些头文件,这些头文件包含程序中需要用到的函数。在例1-1中,第1行包含了头文件 <iostream>,iostream指iostream库。iostream的意思是输入/输出流,由in(输入)、out(输出)的首字母与stream(流)组合而成。

第2行,“using namespace std;”告诉编译器使用std命名空间。命名空间是C++中一个相对新的概念,将在1.13节中详细介绍。

第3行,“// main()是程序开始执行的地方”是一个单行注释。单行注释以“//”开始,在行末结束。

程序的注释是解释性语句。可以在C++代码中包含注释,这将提高代码的可读性和可维护性,所有的编程语言都允许某种形式的注释。

C++支持单行注释和多行注释。注释中的所有字符会被C++编译器忽略。

C++多行注释以“/*”开始,以“*/”结束。单行注释以“//”开始,在行末结束。

第4行,“int main()”是主函数,程序从这里开始执行。

第5行, 只有1个“{”,主程序的代码都包含在“{”和“}”中,以“{”开始,以“}”结束。它是表示程序块的分界符,起到的作用主要是划分区域。

第6行,“cout << “Hello World”;”,cout用于在计算机屏幕上显示信息,是C++中iostream 类型的对象,这行语句的运行结果是在屏幕上显示消息“Hello World”。 与cout对应的cin代表标准输入设备,使用提取运算符“>>”从键盘取得数据,二者都需要iostream.h支持。

第7行,“return 0; ”终止main()函数,并向调用进程返回值0。一般return 0表示程序运行正常并结束;而return -1表示返回一个代数值,一般用在子函数结尾,按照程序开发的惯例,表示该函数失败。在C++中,“;”是语句结束符,也就是说,每个语句必须以“;”结束。

第8行,以“}”结束。

· 1.3.2 C++关键字

表1-1所示为C++中的关键字。这些关键字不能作为常量名、变量名或其他标识符名。

表1-1 C++中的关键字

asm

else

new

this

auto

enum

operator

throw

bool

explicit

private

true

break

export

protected

try

case

extern

public

typedef

catch

false

register

typeid

char

float

reinterpret_cast

typename

class

for

return

union

const

friend

short

unsigned

const_cast

goto

signed

using

continue

if

sizeof

virtual

default

inline

static

void

delete

int

static_cast

volatile

do

long

struct

wchar_t

double

mutable

switch

while

dynamic_cast

namespace

template

1.4 基本数据类型和变量

使用编程语言进行编程时,需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着,当创建一个变量时,就会在内存中保留一些空间。操作系统会根据变量的数据类型来分配内存和决定在保留内存中存储什么。

· 1.4.1 基本的内置类型

C++为程序员提供了种类丰富的内置数据类型和用户自定义的数据类型。表1-2所示为6种基本的C++数据类型。

表1-2 C++中的基本数据类型和描述

类型

关键字

描述

布尔型

bool

值为 true 或 false

字符型

char

通常是一个字符(8位)。字符通过一个整数类型来存储

整型

int

没有小数部分的数字

单精度浮点型

float

1位符号(表明正负),8位指数(用于存储科学记数法中的指数部分),23位小数(用于存储尾数部分)

双精度浮点型

double

1位符号,11位指数,52位小数

无类型

void

表示类型的缺失

表1-3所示为各种数据类型在内存中存储值时需要占用的内存空间和所能存储的值的范围。

表1-3 各种数据类型、占用的内存空间和范围

变量类型

占用内存空间(B)

范围

char

1

−128~127或者0~255

unsigned char

1

0~255

signed char

1

-128~127

int

4

−2147483648~2147483647

unsigned int

4

0~4294967295

signed int

4

−2147483648~2147483647

short int

2

−32768~32767

unsigned short int

2

0~65535

signed short int

2

-32768~32767

long int

8

−9223372036854775808~ 9223372036854775807

signed long int

8

−9223372036854775808 ~ 9223372036854775807

unsigned long int

8

0~18446744073709551615

float

4

1.2e-38~3.4e38 (精度为6~7 位有效数字)

double

8

2.3e-308~1.7e308 (精度为15~16 位有效数字)

long double

16

3.4e-4932~1.1e4932 (精度为18~19位有效数字)

· 1.4.2 变量的声明和初始化

变量的声明就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储。变量的声明指定一个数据类型,并包含该类型的一个或多个变量的列表,具体如下。

    int   i, j, k;
    char   c, ch;
    float  f, salary;
    double d;

“int i, j, k;”声明了变量 i、j 和 k,指示编译器创建名为i、j、k的类型为int的变量。

变量可以在声明的时候被初始化(指定一个初始值)。初始化器由一个等号和一个常量表达式组成,具体如下。

    int d = 3, f = 5; // 声明并初始化 d 和 f
    char x = 'x';  // 变量 x 的值为 'x'

不带初始化的声明:带有静态存储持续时间(static)的变量(全局变量)会被隐式初始化为 NULL(所有字节的值都是0),其他所有变量的初始值是未定义的。

· 1.4.3 变量作用域

变量作用域是程序的一个区域,一般来说有3个地方可以声明变量:在函数或一个代码块内部声明变量(称为局部变量);在函数参数的定义中声明变量(称为形式参数);在所有函数外部声明变量(称为全局变量)。后文会学习什么是函数和参数(见1.8节),这里先讲解何谓局部变量和全局变量。

1.局部变量

在函数或一个代码块内部声明的变量称为局部变量。它们只能被函数内部或者代码块内部的语句使用。例1-2使用了局部变量。

例1-2:局部变量。

    #include <iostream>
    using namespace std;
    int main ()
    {
       // 局部变量声明
       int a, b;
      int c;
       // 变量初始化
       a = 10;
       b = 20;
       c = a + b;
       cout << c;
       return 0;
    }

例1-2运行结果如图1-17所示。

图1-17 例1-2运行结果

2.全局变量

在所有函数外部声明的变量(通常是在程序的开始)称为全局变量。全局变量的值在程序的整个生命周期内都是有效的。全局变量可以被任何函数访问。也就是说,全局变量一旦声明,在整个程序中都是可用的。例1-3使用了全局变量和局部变量。

例1-3:全局变量和局部变量。

    #include <iostream>
    using namespace std;
    // 全局变量声明
    int g;
    int main ()
    {
       // 局部变量声明
       int a, b;
       // 变量初始化
       a = 10;
       b = 20;
       g = a + b;
       cout << g;
       return 0;
    }

例1-3运行结果如图1-18所示。

在程序中,局部变量和全局变量的名称可以相同,但是在函数内局部变量的值会覆盖全局变量的值,见例1-4。

例1-4:局部变量覆盖全局变量。

    #include <iostream>
    using namespace std;
    // 全局变量声明
    int g = 20;
    int main ()
    {
       // 局部变量声明
       int g = 10;
       cout << g;
       return 0;
    }

例1-4运行结果如图1-19所示。

图1-18 例1-3运行结果

图1-19 例1-4运行结果

· 1.4.4 常量定义

C++定义常量有以下两种方式。

#define宏常量: #define 常量名 常量值。通常在代码段前部定义,表示一个常量。

const修饰的变量:const 数据类型 常量名 = 常量值。通常在某个变量前加关键字const,修饰该变量为常量,使之不可修改,见例1-5。

例1-5:常量定义。

    #include<iostream>
    using namespace std;
    //宏常量
    #define day 7
    int main() {
           cout << "一周里总共有 " << day << " 天" << endl;
           //day = 8;  //报错,宏常量不可以修改
           //加const修饰变量后,该变量变成常量,不可修改
           const int month = 12;
           cout << "一年里总共有 " << month << " 个月" << endl;
           //month = 24; //报错,常量是不可以修改的
           return 0;
    }

例1-5运行结果如图1-20所示。

图1-20 例1-5运行结果

二者的区别是const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。

1.5 运算符

运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C++内置了丰富的运算符,并提供以下类型的运算符。

算术运算符:用于处理四则运算。

赋值运算符:用于将表达式的值赋给变量。

比较运算符:用于表达式的比较,并返回一个真值或假值。

逻辑运算符:用于根据表达式的值返回真值或假值。

· 1.5.1 算术运算符

作用:处理四则运算。

表1-4所示为常用的算术运算符,部分使用方法见例1-6、例1-7、例1-8。

表1-4 常用的算术运算符

运算符

含义

示例

结果

+

正号

+3

3

-

负号

-3

-3

+

10 + 5

15

-

10-5

5

*

10 * 5

50

/

10 / 5

2

%

取模(取余)

10 % 3

1

++

前置递增

a=2; b=++a;

a=3; b=3;

++

后置递增

a=2; b=a++;

a=3; b=2;

--

前置递减

a=2; b=--a;

a=1; b=1;

--

后置递减

a=2; b=a--;

a=1; b=2;

例1-6:加减乘除运算。

    //加减乘除
    #include <iostream>
    using namespace std;
    int main() {
       int a1 = 10;
       int b1 = 3;
       cout << a1 + b1 << endl;
       cout << a1 - b1 << endl;
       cout << a1 * b1 << endl;
       cout << a1 / b1 << endl;  //两个整数相除,结果依然是整数
      int a2 = 10;
       int b2 = 20;
       cout << a2 / b2 << endl; 
       int a3 = 10;
       int b3 = 0;
       //cout << a3 / b3 << endl; //报错,除数不可以为0
       //两个小数可以相除
       double d1 = 0.5;
       double d2 = 0.25;
       cout << d1 / d2 << endl;
       return 0;
    }

例1-6运行结果如图1-21所示。

总结:在除法运算中,除数不能为0。

例1-7:取模运算。

    //取模
    #include <iostream>
    using namespace std;
    int main() {
       int a1 = 10;
       int b1 = 3;
       cout << a1 % b1 << endl;
       int a2 = 10;
       int b2 = 20;
       cout << a2 % b2 << endl;
       int a3 = 10;
       int b3 = 0;
       //cout << a3 % b3 << endl; //取模运算时,除数也不能为0
       //两个小数不可以取模
       double d1 = 3.14;
       double d2 = 1.1;
       //cout << d1 % d2 << endl;
       return 0;
    }

例1-7运行结果如图1-22所示。

图1-21 例1-6运行结果

图1-22 例1-7运行结果

总结:只有整型变量可以进行取模运算。

例1-8:递增运算。

    //递增
    #include <iostream>
    using namespace std;
    int main() {
       //后置递增
       int a = 10;
       a++; //等价于a = a + 1
       cout << a << endl; // 11
       //前置递增
       int b = 10;
       ++b;
       cout << b << endl; // 11
       //区别
       //前置递增先对变量进行递增运算,再计算表达式
       int a2 = 10;
       int b2 = ++a2 * 10;
       cout << b2 << endl;
       //后置递增先计算表达式,后对变量进行递增运算
       int a3 = 10;
       int b3 = a3++ * 10;
       cout << b3 << endl;
       return 0;
    }

例1-8运行结果如图1-23所示。

图1-23 例1-8运行结果

总结:前置递增先对变量进行递增运算,再计算表达式;后置递增则先计算表达式,后对变量进行递增运算。

· 1.5.2 赋值运算符

作用:将表达式的值赋给变量。

表1-5所示为常用的赋值运算符,部分使用方法见例1-9。

表1-5 常用的赋值运算符

运算符

含义

示例

结果

=

赋值

a=2; b=3;

a=2; b=3;

+=

加等于

a=0; a+=2;

a=2;

-=

减等于

a=5; a-=3;

a=2;

*=

乘等于

a=2; a*=2;

a=4;

/=

除等于

a=4; a/=2;

a=2;(取整数)

%=

模等于

a=3; a%=2;

a=1;(取余数)

例1-9:赋值运算符。

    #include <iostream>
    using namespace std;
    int main() {
       // =
       int a = 10;
       a = 100;
       cout << "a = " << a << endl;
       // +=
       a = 10;
       a += 2; // a = a + 2;
       cout << "a = " << a << endl;
       // -=
       a = 10;
       a -= 2; // a = a - 2
       cout << "a = " << a << endl;
       // *=
       a = 10;
       a *= 2; // a = a * 2
       cout << "a = " << a << endl;
       // /=
       a = 10;
       a /= 2;  // a = a / 2;
       cout << "a = " << a << endl;
       // %=
       a = 10;
       a %= 2;  // a = a % 2;
       cout << "a = " << a << endl;
       return 0;
    }

例1-9运行结果如图1-24所示。

图1-24 例1-9运行结果

· 1.5.3 比较运算符

作用:表达式的比较,并返回一个真值或假值。

表1-6所示为常用的比较运算符,部分使用方法见例1-10。

表1-6 常用的比较运算符

运算符

含义

示例

结果

= =

等于

4 = = 3

0

!=

不等于

4 != 3

1

<

小于

4 < 3

0

>

大于

4 > 3

1

<=

小于等于

4 <= 3

0

>=

大于等于

4 >= 1

1

例1-10:比较运算符。

    #include <iostream>
    using namespace std;
    int main() {
       int a = 10;
       int b = 20;
       cout << (a = = b) << endl; // 0 
       cout << (a != b) << endl; // 1
       cout << (a > b) << endl; // 0
       cout << (a < b) << endl; // 1
       cout << (a >= b) << endl; // 0
       cout << (a <= b) << endl; // 1
       return 0;
    }

注意:C++的比较运算中,“真”用数字“1”来表示,“假”用数字“0”来表示。

例1-10运行结果如图1-25所示。

图1-25 例1-10运行结果

· 1.5.4 逻辑运算符

作用:根据表达式的值返回真值或假值。

表1-7所示为常用的逻辑运算符,部分使用方法见例1-11、例1-12、例1-13。

表1-7 常用的逻辑运算符

运算符

含义

示例

结果

!

!a

a为假,则!a为真;a为真,则!a为假

&&

a && b

a和b都为真,则结果为真,否则为假

||

a || b

a和b有一个为真,则结果为真;二者都为假时,结果为假

例1-11:逻辑非运算符。

    //逻辑非运算符  
    #include <iostream>
    using namespace std;
    int main() {
      int a = 10;
       cout << !a << endl; // 0
       cout << !!a << endl; // 1
       return 0;
    }

例1-11运行结果如图1-26所示。

总结:真变假,假变真。

例1-12:逻辑与运算符。

    //逻辑与运算符  
    #include <iostream>
    using namespace std;
    int main() {
       int a = 10;
       int b = 10;
       cout << (a && b) << endl;// 1
       a = 10;
       b = 0;
       cout << (a && b) << endl;// 0 
       a = 0;
       b = 0;
       cout << (a && b) << endl;// 0
       return 0;
    }

例1-12运行结果如图1-27所示。

图1-26 例1-11运行结果

图1-27 例1-12运行结果

总结:同真为真,其余为假。

例1-13:逻辑或运算符。

    //逻辑或运算符  
    #include <iostream>
    using namespace std;
    int main() {
       int a = 10;
       int b = 10;
       cout << (a || b) << endl;// 1
       a = 10;
       b = 0;
       cout << (a || b) << endl;// 1 
       a = 0;
       b = 0;
       cout << (a || b) << endl;// 0
       return 0;
    }

例1-13运行结果如图1-28所示。

图1-28 例1-13运行结果

总结:同假为假,其余为真。

1.6 控制台数据输入和输出

控制台也叫命令行。本节将介绍命令行的数据输入和输出。

数据输入。

作用:从键盘获取数据。

关键字:cin。

语法: cin >> 变量。

数据输出。

作用:屏幕显示数据。

关键字:cout。

语法:cout << 变量。

具体使用方法见例1-14。

例1-14:数据输入和输出。

    #include <iostream>
    using namespace std;
    int main(){
         //整型输入
         int a;
         cout << "请输入整型变量:" << endl;
         cin >> a;
         cout << a << endl;
         //浮点型输入
         double d;
         cout << "请输入浮点型变量:" << endl;
         cin >> d;
         cout << d << endl;
         //字符型输入
         char ch;
         cout << "请输入字符型变量:" << endl;
         cin >> ch;
         cout << ch << endl;
         //字符串型输入
         string str;
         cout << "请输入字符串型变量:" << endl;
        cin >> str;
         cout << str << endl;
         //布尔型输入
         bool flag;
         cout << "请输入布尔型变量:" << endl;
         cin >> flag;
         cout << flag << endl;
         return 0;
    }

例1-14运行结果如图1-29所示。

图1-29 例1-14运行结果

1.7 结构化程序设计

C++支持最基本的3种程序运行结构:顺序结构、选择结构、循环结构。

顺序结构:程序按顺序执行,不发生语句跳转,此处不单独介绍。

选择结构:根据条件是否满足,有选择地执行相应的代码。

循环结构:根据条件是否满足,循环多次执行某段代码。

· 1.7.1 选择结构

1.if语句

作用:执行满足条件的语句。

if语句的3种形式如下。

单行格式if语句。

多行格式if语句。

多条件的if语句。

(1)单行格式if语句:if(条件){条件满足时执行的语句},流程如图1-30所示,使用方法见例1-15。

图1-30 单行格式if语句条件判断流程

例1-15:单行格式if语句。

    #include <iostream>
    using namespace std;
    int main() {
       //选择结构—单行格式if语句
       //输入一个分数,如果分数大于600分,视为考上本科第一批录取的大学(一本大学),并输出
       int score = 0;
       cout << "请输入一个分数:" << endl;
       cin >> score;
       cout << "您输入的分数为: " << score << endl;
       //if语句
       //注意事项:在if判断语句后面不要加分号
       if (score > 600)
       {
           cout << "我考上了一本大学!!!" << endl;
       }
       return 0;
    }

例1-15运行结果如图1-31所示。

注意:if条件表达式后不要加分号。

(2)多行格式if语句:if(条件){条件满足时执行的语句}else{条件不满足时执行的语句},流程如图1-32所示,使用方法见例1-16。

图1-31 例1-15运行结果

图1-32 多行格式if语句条件判断流程

例1-16:多行格式if语句。

    #include <iostream>
    using namespace std;
    int main() {
       int score = 0;
       cout << "请输入考试分数:" << endl;
       cin >> score;
       if (score > 600)
       {
           cout << "我考上了一本大学" << endl;
       }
       else
       {
          cout << "我未考上一本大学" << endl;
       }
       return 0;
    }

例1-16运行结果如图1-33所示。

(3)多条件的if语句:if(条件1){条件1满足时执行的语句}else if(条件2){条件2满足时执行的语句}...else{都不满足时执行的语句},流程如图1-34所示,使用方法见例1-17。

图1-33 例1-16运行结果

图1-34 多条件的if语句条件判断流程

例1-17:多条件的if语句。

    #include <iostream>
    using namespace std;
    int main() {
       int score = 0;
       cout << "请输入考试分数:" << endl;
       cin >> score;
       if (score > 600)
       {
           cout << "我考上了一本大学" << endl;
       }
       else if (score > 500)
       {
           cout << "我考上了二本大学" << endl;
       }
       else if (score > 400)
       {
           cout << "我考上了三本大学" << endl;
       }
       else
       {
           cout << "我未考上本科" << endl;
       }
       return 0;
    }

例1-17运行结果如图1-35所示。

总结:在if语句中,可以嵌套使用if语句,以达到更精确的条件判断。

2.三目运算符

作用:通过三目运算符实现简单的判断。

语法:表达式1 ?表达式2:表达式3。

解释:如果表达式1的值为真,执行表达式2,并返回表达式2的结果;如果表达式1的值为假,执行表达式3,并返回表达式3的结果。

具体使用方法见例1-18。

例1-18:三目运算符。

    #include <iostream>
    using namespace std;
    int main() {
       int a = 10;
       int b = 20;
       int c = 0;
       c = a > b ? a : b;
       cout << "c = " << c << endl;
       //C++中三目运算符返回的是变量,可以继续赋值
       (a > b ? a : b) = 100;
       cout << "a = " << a << endl;
       cout << "b = " << b << endl;
       cout << "c = " << c << endl;
       return 0;
    }

例1-18运行结果如图1-36所示。

图1-35 例1-17运行结果

图1-36 例1-18运行结果

总结:和if语句相比较,三目运算符的优点是短小、整洁;缺点是如果用嵌套,结构不清晰。

3.switch语句

作用:执行多条件分支语句。

语法:

    switch(表达式)
    {
       case 结果1:执行语句;break;
       case 结果2:执行语句;break;
       ...
       default:执行语句;break;
    }

具体使用方法见例1-19。

例1-19:switch语句。

    #include <iostream>
    using namespace std;
    int main() {
       //请给电影评分 
       //10~9: 经典   
       // 8: 非常好
       // 7~6: 一般
       // 5分及以下: 较差
       int score = 0;
       cout << "请给电影打分" << endl;
       cin >> score;
       switch (score)
       {
       case 10:
       case 9:
             cout << "经典" << endl;
             break;
       case 8:
             cout << "非常好" << endl;
             break;
       case 7:
       case 6:
             cout << "一般" << endl;
             break;
       default:
             cout << "较差" << endl;
             break;
       }
       return 0;
    }

例1-19运行结果如图1-37所示。

图1-37 例1-19运行结果

注意1:switch语句中表达式的类型只能是整型或者字符型。

注意2:case里如果没有break,那么程序会一直向下执行。

总结:与if语句相比较,在多条件判断时,switch语句的结构更清晰,执行效率更高;缺点是switch语句不可以判断区间。

· 1.7.2 循环结构

1.while循环语句

作用:满足循环条件,执行循环语句。

语法:while(循环条件){执行语句}。

解释:只要循环条件的结果为真,就执行循环语句,否则跳出循环。流程如图1-38所示,使用方法见例1-20。

例1-20:while循环语句。

    #include <iostream>
    using namespace std;
    int main() {
       int num = 0;
       while (num < 10)
       {
           cout << "num = " << num << endl;
           num++;
       }
       return 0;
    }

例1-20程序运行结果如图1-39所示。

图1-38 while循环语句流程

图1-39 例1-20运行结果

注意:在执行循环语句时,程序必须提供跳出循环的出口,否则会出现死循环,即程序不能结束运行。

2.do-while循环语句

作用:满足循环条件,执行循环语句。

语法:do{执行语句} while(循环条件)。

流程如图1-40所示,具体使用方法见例1-21。

例1-21:do-while循环语句。

    #include <iostream>
    using namespace std;
    int main() {
       int num = 0;
       do
       {
           cout << "num=" <<num << endl;
           num++;
       } while (num < 10);
       return 0;
    }

例1-21运行结果如图1-41所示。

图1-40 do-while循环语句流程

图1-41 例1-21运行结果

总结:do-while循环语句与while循环语句的区别在于,do-while循环语句先执行一次循环语句,再判断循环条件。

3.for循环语句

作用:满足循环条件,执行循环语句。

语法:for(起始表达式;条件表达式;末尾循环体) {循环语句}。

具体使用方法见例1-22。

例1-22:for循环语句。

    #include <iostream>
    using namespace std;
    int main() {
       for (int i = 0; i < 10; i++)
       {
           cout << i << endl;
       }
       return 0;
    }

例1-22运行结果如图1-42所示。

图1-42 例1-22运行结果

注意:for循环语句中的表达式,要用分号进行分隔。

总结:while循环语句、 do-while循环语句、for循环语句都是开发中常用的循环语句,for循环语句结构清晰,比较常用。

4.嵌套循环

作用:在循环体中再嵌套一层循环,解决一些实际问题。

嵌套循环语句的具体使用方法见例1-23。

例1-23:嵌套循环语句。

    #include <iostream>
    using namespace std;
    int main() {
       //外层循环执行1次,内层循环执行1轮
       for (int i = 0; i < 10; i++)
       {
           for (int j = 0; j < 10; j++)
           {
                     cout << "*" << " ";
           }
           cout << endl;
       }
       return 0;
    }

例1-23运行结果如图1-43所示。

图1-43 例1-23运行结果

· 1.7.3 跳转语句

在循环语句执行过程中,跳转语句用于实现程序语句的跳转。在C++中,跳转语句有break语句、continue语句、goto语句3种,下面分别进行介绍。

1.break语句

作用:跳出选择结构或者循环结构。

break语句使用的情况有以下3种。

出现在switch条件语句中,作用是终止case并跳出switch条件语句。

出现在循环语句中,作用是跳出当前的循环语句。

出现在嵌套循环语句中,作用是跳出最近的内层循环语句。

具体使用方法见例1-24、例1-25、例1-26。

例1-24:跳转语句。

    #include<iostream>
    using namespace std;
    int main() {
      //在switch 语句中使用break
       cout << "请选择您挑战副本的难度:" << endl;
       cout << "1.普通" << endl;
       cout << "2.中等" << endl;
       cout << "3.困难" << endl;
       int num = 0;
       cin >> num;
       switch (num)
       {
       case 1:
             cout << "您选择的是普通难度" << endl;
             break;
       case 2:
             cout << "您选择的是中等难度" << endl;
             break;
       case 3:
             cout << "您选择的是困难难度" << endl;
             break;
       }
       return 0;
    }

例1-24运行结果如图1-44所示。

例1-25:跳出循环语句。

    #include <iostream>
    using namespace std;
    int main() {
       //在循环语句中使用break
       for (int i = 0; i < 10; i++)
       {
           if (i == 5)
           {
                     break; //跳出循环语句
           }
           cout << i << endl;
       }
       return 0;
    }

例1-25运行结果如图1-45所示。

图1-44 例1-24运行结果

图1-45 例1-25运行结果

例1-26:跳转语句。

    #include <iostream>
    using namespace std;
    int main() {
       //在嵌套循环语句中使用break,退出内层循环
       for (int i = 0; i < 10; i++)
       {
           for (int j = 0; j < 10; j++)
           {
                     if (j == 5)
                     {
                             break;
                     }
                     cout << "*" << " ";
           }
           cout << endl;
       }
       return 0;
    }

例1-26运行结果如图1-46所示。

图1-46 例1-26运行结果

2.continue语句

作用:在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环,具体使用方法见例1-27。

例1-27:continue语句。

    #include <iostream>
    using namespace std;
    int main() {
       for (int i = 0; i < 100; i++)
       {
           if (i % 2 == 0)
           {
                     continue;
           }
           cout << i << endl;
       }
       return 0;
    }

例1-27运行结果如图1-47所示。

注意:continue语句并没有使整个循环终止,而break语句会跳出循环。

3.goto语句

作用:可以无条件跳转语句。

语法:goto 标记。

解释:如果标记的名称存在,执行到goto语句时会跳转到标记的位置。

具体使用方法见例1-28。

例1-28:goto语句。

    #include <iostream>
    using namespace std;
    int main() {
       cout << "1" << endl;
       goto FLAG;
       cout << "2" << endl;
       cout << "3" << endl;
       cout << "4" << endl;
       FLAG:
       cout << "5" << endl;
       return 0;
    }

例1-28运行结果如图1-48所示。

图1-47 例1-27运行结果

图1-48 例1-28运行结果

注意:在程序中不建议使用goto语句,以免造成程序流程混乱。

1.8 参数和函数

参数也叫参变量,它就是一个变量,传递参数就是传递变量。关于参数,可以这样去理解:魔法师可以根据观众的要求变出鸡、鸭、猫等动物,当观众要求魔法师变出鸡时,观众说出的“鸡”对魔法师而言就是参数。

函数是一组一起执行一个任务的语句。每个C++程序都至少有一个函数,即主函数 main(),所有程序都可以定义其他额外的函数。可以把代码划分到不同的函数中,如何划分代码到不同的函数中可自行决定,但在逻辑上,划分通常是根据每个函数执行一个特定的任务进行的。函数的声明告诉编译器函数的名称、返回类型和参数。函数的定义提供了函数的实际主体。

· 1.8.1 函数的定义

函数的定义一般主要由5个部分组成:返回值类型、函数名、参数列表、函数体语句、return 表达式。

具体语法如下。

    返回值类型 函数名 (参数列表)
    {
          函数体语句
          return表达式
    }

返回值类型 :一个函数可以返回一个值,有些函数只是执行所需的操作而不返回值,在这种情况下,返回值类型是void。

函数名:这是函数的实际名称,自定义函数名须满足变量名命名规范。

参数列表:使用该函数时传入的数据。

函数体语句:“{}”内的代码,函数内需要执行的语句。

return表达式:和返回值类型有关,函数执行完后,返回相应的数据。

具体定义方法见例1-29。

例1-29:定义一个加法函数,实现两个数相加。

    //函数定义
    #include <iostream>
    using namespace std;
    int add(int num1, int num2)//包括了2个形式参数(简称形参)
    {
       int sum = num1 + num2;
       return sum;//函数返回值
    }

· 1.8.2 函数调用

功能:创建C++函数时,会定义函数的任务,然后通过调用函数来完成已定义的任务。当程序调用函数时,程序控制权会被转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束标记花括号位置时,会把程序控制权交还给主程序。调用函数时,传递所需参数,如果函数返回一个值,则可以存储该返回值。

语法:函数名(参数)。

具体调用方法见例1-30。

例1-30:函数调用。

    #include<iostream>
    using namespace std;
    //函数定义
    int add(int num1, int num2) //定义中的num1、num2称为形式参数,简称形参
    {
       int sum = num1 + num2;
       return sum;
    }
    int main() {
       int a = 10;
       int b = 10;
       //调用add()函数
       int sum = add(a, b);//调用时的a、b称为实际参数,简称实参
       cout << "sum = " << sum << endl;
       a = 100;
       b = 100;
       sum = add(a, b);
       cout << "sum = " << sum << endl;
       return 0;
    }

例1-30运行结果如图1-49所示。

图1-49 例1-30运行结果

总结:函数定义里圆括号内的参数称为形式参数(简称形参),函数调用时传入的参数称为实际参数(简称实参)。

· 1.8.3 值传递

C语言的参数传递方法有3种:值传递、地址传递、引用传递,具体如下。

值传递:在函数调用时,实参以数值形式传给形参。在进行值传递时,如果形参发生改变,并不会影响实参。

地址传递:在调用函数的时候,将参数的值所在的地址复制一份过去。因此,被调用函数对参数地址的值进行修改会影响原来的值。

地址传递是比较难理解的概念,对此进行对比说明:关于值传递,可这样去理解,子函数以形参形式从主函数中传入数据,但这个数据只和形参发生传值后就立刻返回,而后子函数发生的数据变化不会影响主函数的数据;关于地址传递,子函数以形参形式从主函数中传入的是存放数据的地址,因此子函数的数据变化就是主函数的数据变化。

进一步通过“孙悟空”和“杨戬”来举例说明。定义孙悟空为一个变量,杨戬为一个函数,变量孙悟空可利用分身术产生一个形参传递给函数杨戬,这时候为值传递。函数杨戬怎样收拾变量孙悟空的分身(形参,只传递值的副本)都不会对变量孙悟空本身产生影响,但如果变量孙悟空把真身的地址告诉了函数杨戬(地址传递,传递变量孙悟空在内存中的地址),函数杨戬就可以根据地址找到变量孙悟空本身,这时候函数杨戬对变量孙悟空的操作就会改变孙悟空的值了。

引用传递:引用是变量的一个别名,调用别名和调用变量是完全一样的,其效果和地址传递一样。

这里先对值传递进行举例解释,见例1-31。地址传递举例请见1.10.4小节,引用传递举例请见2.2.2小节。

例1-31:值传递。

    #include<iostream>
    using namespace std;
    void swap1(int num1, int num2)//num1和num2为形参
    {
           cout<<"swap1中参数交换前: num1= " << num1 <<" num2 ="<<num2<< endl;
           int temp = num1;
           num1 = num2;
           num2 = temp;
           cout<<"swap1中参数交换后: num1= " << num1 <<" num2 ="<<num2<< endl;
           return ; //当函数声明时,如果函数返回类型为void,则该函数不需要返回值,此处可不写return
    }
    int main() {
           int a = 10;
           int b = 20;
           swap1(a, b);//调用函数swap(), a和b为实参
           cout << "调用swap1交换后main中的 a= " << a<<" b="<< b<< endl;
           return 0;
    }

例1-31运行结果如图1-50所示。

图1-50 例1-31运行结果

总结:在进行值传递时,形参是改变不了实参(传递前的变量原值)的。

· 1.8.4 函数的常见样式

常见的函数样式有以下4种。

无参(数)无返(回值)。

有参无返。

无参有返。

有参有返。

具体见例1-32。

例1-32:函数常见样式。

    #include<iostream>
    using namespace std;
    //函数常见样式
    //1.无参无返
    void test01()
    {
      //void a = 10; //无类型不可以创建变量,原因是它无法分配内存
       cout << "this is test01" << endl;
       test01();//函数调用
    }
    //2.有参无返
    void test02(int a)
    {
       cout << "this is test02" << endl;
       cout << "a = " << a << endl;
    }
    //3.无参有返
    int test03()
    {
       cout << "this is test03 " << endl;
       return 10;
    }
    //4.有参有返
    int test04(int a, int b)
    {
       cout << "this is test04 " << endl;
       int sum = a + b;
       return sum;
    }

· 1.8.5 函数的声明

作用:告诉编译器函数名称和如何调用函数。函数的实际主体可以单独定义。

函数可以声明多次,但是函数只能定义一次。

在函数的声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面也是有效的声明。

    int max(int, int);

具体声明方法见例1-33。

例1-33:函数的声明。

    #include<iostream>
    using namespace std;
    //声明可以多次,定义只能一次
    int max(int a, int b);
    int max(int a, int b);
    int main() {
       int a = 100;
       int b = 200;
      cout << max(a, b) << endl;
       return 0;
    }
    //定义
    int max(int a, int b)
    {
       return a > b ? a : b;
    }

例1-33运行结果如图1-51所示。

图1-51 例1-33运行结果

· 1.8.6 外部文件

作用:调用外部文件可以让代码结构更加清晰,也就是所谓的用分文件保存源文件代码。

函数分文件编写一般有4个步骤。

创建扩展名为.h的头文件。

创建扩展名为.cpp的源文件。

在头文件中写函数的声明。

在源文件中写函数的定义。

具体使用方法见例1-34。

例1-34:外部文件。

    //swap.h文件
    #include<iostream>
    using namespace std;
    //实现两个数字交换的函数声明
    void swap(int a, int b);
    //swap.cpp文件
    #include "swap.h"
    void swap(int a, int b)
    {
       int temp = a;
       a = b;
       b = temp;
       cout << "a = " << a << endl;
       cout << "b = " << b << endl;
    }
    //main()函数文件
    #include "swap.h"
    int main() {
       int a = 100;
       int b = 200;
      swap(a, b);
       return 0;
    }

1.9 数组与字符串

· 1.9.1 数组

数组是一个可以存储固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据的,但往往被认为是一系列相同类型的变量。

数组的声明并不是声明一个个单独的变量,如 number0, number1, …, number99,而是声明一个数组变量,如 numbers,然后使用 numbers[0], numbers[1],…, numbers[99] 来代表一个个单独的变量。数组中的特定元素可以通过索引访问。

所有数组的元素在内存中是连续存储的,占据连续的内存地址。最低的地址对应第一个元素,最高的地址对应最后一个元素。

1.一维数组

一维数组定义的3种方式。

数据类型 数组名[ 元素个数 ];。

数据类型 数组名[ 元素个数 ] = { 值1,值2,值3, …};。

数据类型 数组名[ ] = { 值1,值2,值3, …};。

具体使用方法见例1-35、例1-36。

例1-35:数组的定义和赋值。

    #include <iostream>
    using namespace std;
    int main() {
       //定义方式1
       //数据类型 数组名[元素个数];
       int score[10];
       //利用下标赋值
       score[0] = 100;
       score[1] = 99;
       score[2] = 85;
       //利用下标输出
       cout << score[0] << endl;
       cout << score[1] << endl;
       cout << score[2] << endl;
       //定义方式2
       //数据类型 数组名[元素个数] =  {值1,值2,值3,…};
       //如果{}内不足10个数据,剩余数据用0补全
       int score2[10] = { 100, 90,80,70,60,50,40,30,20,10 };
      //逐个输出
       //cout << score2[0] << endl;
       //cout << score2[1] << endl;
       //一个一个输出太麻烦,因此可以利用循环语句进行输出
       for (int i = 0; i < 10; i++)
       {
           cout << score2[i] << endl;
       }
       //定义方式3
       //数据类型 数组名[] =  {值1,值2 ,值3, …};
       int score3[] = { 100,90,80,70,60,50,40,30,20,10 };
       for (int i = 0; i < 10; i++)
       {
           cout << score3[i] << endl;
       }
       return 0;
    }

例1-35运行结果如图1-52所示。

图1-52 例1-35运行结果

总结1:数组名的命名规范与变量名的命名规范一致,不要和变量重名。

总结2:数组中的下标是从0开始的。

一维数组名称有以下2个用途。

可以统计整个数组在内存中的长度。

可以获取数组在内存中的首地址。

具体使用方法见例1-36。

例1-36:一维数组。

    #include <iostream>
    using namespace std;
    int main() {
       //数组名用途
      //1.可以获取整个数组占用内存空间的大小
       int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
       cout << "整个数组所占内存空间为:" << sizeof(arr) << endl;
       cout << "每个元素所占内存空间为:" << sizeof(arr[0]) << endl;
       cout << "数组的元素个数为:" << sizeof(arr) / sizeof(arr[0]) << endl;
       //2.可以通过数组名获取到数组首地址
       cout << "数组首地址为:" << (long long)arr << endl;//64 位计算机用 long long,32 位用 int
       cout << "数组中第一个元素地址为:" << (long long)&arr[0] << endl;
       cout << "数组中第二个元素地址为:" << (long long)&arr[1] << endl;
       //arr = 100; 错误,数组名是常量,因此不可以赋值
       return 0;
    }

例1-36运行结果如图1-53所示。

图1-53 例1-36运行结果

注意:数组名是常量,不可以赋值。

总结1:直接输出数组名,可以查看数组所占内存的首地址。

总结2:对数组调用sizeof()操作符(非函数),可以获取整个数组占内存空间的大小。

2.二维数组

二维数组就是在一维数组上多加一个维度。如二维矩阵就是一个二维数组。

二维数组定义的4种方式。

数据类型 数组名[行数][列数];。

数据类型 数组名[行数][列数] = { {数据1, 数据2 } , {数据3, 数据4 } };。

数据类型 数组名[行数][列数] = {数据1, 数据2, 数据3, 数据4};。

数据类型 数组名[ ][列数] = {数据1, 数据2, 数据3, 数据4};。

以上4种定义方式,第2种更加直观,可提高代码的可读性。

具体使用方法见例1-37、例1-38、例1-39。

例1-37:二维数组。

    #include <iostream>
    using namespace std;
    int main() {
       //方式1  
       //数组类型 数组名 [行数][列数]
       int arr[2][3];
       arr[0][0] = 1;
       arr[0][1] = 2;
      arr[0][2] = 3;
       arr[1][0] = 4;
       arr[1][1] = 5;
       arr[1][2] = 6;
       for (int i = 0; i < 2; i++)
       {
           for (int j = 0; j < 3; j++)
           {
                     cout << arr[i][j] << " ";
           }
           cout << endl;
       }
       //方式2 
       //数据类型 数组名[行数][列数] = { {数据1, 数据2 } , {数据3, 数据4 } };
       int arr2[2][3] =
       {
           {1,2,3},
           {4,5,6}
       };
       //方式3
       //数据类型 数组名[行数][列数] = { 数据1, 数据2, 数据3, 数据4  };
       int arr3[2][3] = { 1,2,3,4,5,6 }; 
       //方式4 
       //数据类型 数组名[][列数] = { 数据1, 数据2, 数据3, 数据4  };
       int arr4[][3] = { 1,2,3,4,5,6 };
       return 0;
    }

例1-37运行结果如图1-54所示。

图1-54 例1-37运行结果

总结:在定义二维数组时,如果对数据进行了初始化,可以省略行数。

下面通过例1-38对如何查看二维数组所占内存空间和如何获取二维数组首地址等操作进行说明。

例1-38:二维数组操作。

    #include <iostream>
    using namespace std;
    int main() {
       //定义二维数组
       int arr[2][3] =
       {
           {1,2,3},
           {4,5,6}
       };
       cout << "二维数组大小: " << sizeof(arr) << endl;
      cout << "二维数组一行大小: " << sizeof(arr[0]) << endl;
       cout << "二维数组元素大小: " << sizeof(arr[0][0]) << endl;
       cout << "二维数组行数: " << sizeof(arr) / sizeof(arr[0]) << endl;
       cout << "二维数组列数: " << sizeof(arr[0]) / sizeof(arr[0][0]) << endl;
       //地址
       cout << "二维数组首地址:" << arr << endl;
       cout << "二维数组第一行地址:" << arr[0] << endl;
       cout << "二维数组第二行地址:" << arr[1] << endl;
       cout << "二维数组第一个元素地址:" << &arr[0][0] << endl;
       cout << "二维数组第二个元素地址:" << &arr[0][1] << endl;
       return 0;
    }

例1-38运行结果如图1-55所示。

图1-55 例1-38运行结果

总结1:二维数组数组名就是这个数组的首地址。

总结2:对二维数组调用sizeof()函数时,可以获取整个二维数组占用的内存空间大小。

接下来通过一个二维数组应用案例—成绩表,进一步介绍二维数组。

案例描述:有3名同学(张三、李四、王五),在一次考试中的成绩如表1-8所示,请分别输出3名同学的总成绩。

表1-8 成绩表

姓名

语文

数学

英语

张三

100

100

100

李四

90

50

100

王五

60

70

80

具体的案例代码见例1-39。

例1-39:二维数组的应用。

    #include <iostream>
    using namespace std;
    int main() {
       int scores[3][3] =
       {
           {100,100,100},
           {90,50,100},
           {60,70,80},
       };
      string names[3] = { "张三","李四","王五" };
       for (int i = 0; i < 3; i++)
       {
           int sum = 0;
           for (int j = 0; j < 3; j++)
           {
                     sum += scores[i][j];
           }
           cout << names[i] << "同学总成绩为: " << sum << endl;
       }
       return 0;
    }

例1-39运行结果如图1-56所示。

图1-56 例1-39运行结果

· 1.9.2 字符串

1.数组型字符串

字符串实际上是使用字符‘\0’终止的一维字符数组。因此,一个以‘\0’结尾的字符串,包含了组成字符串的字符。

下面的声明和初始化创建了一个 “Hello” 字符串。由于在数组的末尾存储了空字符,所以字符数组的字符数比单词 “Hello” 的字符数多1。

    char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

依据数组初始化规则,可以把上面的语句写成以下语句。

    char greeting[] = "Hello";

C++中定义的字符串的内存表示如图1-57所示。

图1-57 字符串的内存表示

字符串的定义和赋值见例1-40。

例1-40:字符串的定义和赋值。

    #include <iostream>
    using namespace std;
    int main ()
    {
       char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; 
       cout << "Greeting message: ";
       cout << greeting << endl;
       return 0;
    }

例1-40运行结果如图1-58所示。

图1-58 例1-40运行结果

C++提供了一些函数来操作以‘\0’结尾的字符串,如表1-9所示。

表1-9 字符串操作函数与功能

函数

功能

strcpy(s1, s2)

复制字符串 s2 到字符串 s1

strcat(s1, s2)

连接字符串 s2 到字符串 s1 的末尾

strlen(s1)

返回字符串 s1 的长度

strcmp(s1, s2)

如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2,则返回值小于 0;如果 s1>s2,则返回值大于 0

例1-41使用了上述的一些函数。

例1-41:字符串函数。

    #include <iostream>
    #include <cstring> //此处需要加入头文件cstring或者string.h
    using namespace std; 
    int main ()
    {
       char str1[11] = "Hello";
       char str2[11] = "World";
       char str3[11];
       int  len ;
       // 复制 str1 到 str3
       strcpy( str3, str1);
       cout << "strcpy( str3, str1) : " << str3 << endl;
       // 连接 str1 和 str2
       strcat( str1, str2);
       cout << "strcat( str1, str2): " << str1 << endl;
       // 连接后,返回str1 的总长度
       len = strlen(str1);
      cout << "strlen(str1) : " << len << endl;
       return 0;
    }

例1-41运行结果如图1-59所示。

图1-59 例1-41运行结果

2.string类型字符串

C++标准库提供了string类型,支持上述所有的操作,另外还增加了其他功能。下面将学习C++标准库中的这个类,首先来看例1-42。

例1-42:字符串操作。

    #include <iostream>
    #include <string.h>
    using namespace std; 
    int main ()
    {
       string str1 = "Hello";//声明类并创建了对象str1
       string str2 = "World";
       string str3;
       int  len ;
       // 复制str1到str3
       str3 = str1;
       cout << "str3 : " << str3 << endl; 
       // 连接str1和str2
       str3 = str1 + str2;
       cout << "str1 + str2 : " << str3 << endl; 
       // 连接后,返回str3的总长度
       len = str3.size();
       cout << "str3.size() :  " << len << endl;
       return 0;
    }

例1-42运行结果如图1-60所示。

图1-60 例1-42运行结果

如果读者无法透彻地理解这个实例,可能是因为到目前为止我们还没有讨论类和对象,所以现在读者可以粗略地看看这个实例,等理解了面向对象的概念之后再回头来分析这个实例。

1.10 指针

· 1.10.1 什么是指针

指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。就像其他变量或常量一样,用户必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式如下。

    type *var-name;

在这里:type 是指针的基类,它必须是一个有效的C++数据类型;var-name是指针变量的名称;用来声明指针的星号*与乘法中使用的星号是相同的,但是在这个语句中,星号用来指定一个变量是指针。以下是有效的指针声明。

    int         *ip;     /* 一个整型的指针 */
    double *dp;    /* 一个双精度浮点型的指针 */
    float     *fp;     /* 一个单精度浮点型的指针 */
    char      *ch;    /* 一个字符型的指针 */

不管是整型、浮点型、字符型还是其他数据类型的指针,都是一个代表内存地址长度的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

· 1.10.2 指针的使用

使用指针时会频繁进行以下几个操作。

定义一个指针变量。

把变量地址赋值给指针或通过关键字new定义一个新变量并返回变量的地址。

访问指针变量中可用地址的值,这些值是通过使用运算符“*”返回的位于操作数所指定地址的变量的值。

例1-43涉及这些操作。

例1-43:指针的使用。

    #include <iostream>
    using namespace std;
    int main ()
    {
       int  var = 20;    //实际变量的声明
       int  *ip;         // 指针变量的声明
       ip = &var;        //在指针变量中存储 var 的地址
      int  *ip_new;    //通过关键字new定义新变量
       //new的语法:new 类型(初始值)
       ip_new = new int(10);//新建一个整型变量并赋初始值10
       cout << "Value of var variable: ";
       cout << var << endl;
       //输出在指针变量中存储的地址
       cout << "Address stored in ip variable: ";
       cout << ip << endl;
       cout<<ip_new<<endl;
       // 访问指针变量地址的值
       cout << "Value of *ip variable: ";
       cout << *ip << endl;
       cout<<*ip_new<<endl;
       return 0;
    }

例1-43运行结果如图1-61所示。

图1-61 例1-43运行结果

· 1.10.3 指针和数组

下面通过例1-44说明如何利用指针来访问数组中的元素。

例1-44:利用指针访问数组中的元素。

    #include <iostream>
    using namespace std;
    int main() {
       int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
       int * p = arr;  //指向数组的指针
       cout << "第一个元素:" << arr[0] << endl;
       cout << "指针访问第一个元素:" << *p << endl;
       for (int i = 0; i < 10; i++)
       {
           //利用指针遍历数组
           cout << *p << endl;
           p++;
       }
       return 0;
    }

例1-44运行结果如图1-62所示。

图1-62 例1-44运行结果

· 1.10.4 指针和函数

前文对地址传递进行了概念上的描述,学习了指针。由于指针指向地址,因此可利用指针作为函数参数,传递参数地址,达到修改实参的值的目的。下面通过例1-45进行说明。

例1-45:利用指针作为函数参数。

    //值传递
    #include <iostream>
    using namespace std;
    void swap1(int a ,int b)
    {
       int temp = a;
       a = b; 
       b = temp;
    }
    //地址传递
    void swap2(int * p1, int *p2)
    {
       int temp = *p1;
       *p1 = *p2;
       *p2 = temp;
    }
    int main() {
       int a = 10;
       int b = 20;
       swap1(a, b); //值传递不会改变实参的值
       swap2(&a, &b); //地址传递会改变实参的值
       cout << "a = " << a << endl;
       cout << "b = " << b << endl;
       return 0;
    }

例1-45运行结果如图1-63所示。

图1-63 例1-45运行结果

总结:如果不想修改实参的值,就用值传递;如果想修改实参的值,就用地址传递或引用传递。

1.11 结构体

结构体是C++中用户自定义的可用的数据类型,它允许用户存储不同类型的数据项。

结构体用于表示一条记录,假设用户想要跟踪图书馆中书的动态,可能需要跟踪每本书的下列属性。

Title:标题。

Author:作者。

Subject:类目。

Book ID:书的ID。

· 1.11.1 结构体的定义和使用

语法:struct 结构体名 {结构体成员列表}。

通过结构体创建变量的方式有以下3种。

struct 结构体名 变量名。

struct 结构体名 变量名 = {成员1值, 成员2值, …}。

定义结构体时顺便创建变量。

具体定义和使用方法见例1-46。

例1-46:结构体的定义和使用。

    //结构体定义
    #include <iostream>
    using namespace std;
    struct student
    {
       //成员列表
       string name;  //姓名
       int age;      //年龄
       int score;    //分数
    }stu3; //结构体变量创建方式3 
    int main() {
       //结构体变量创建方式1
       struct student stu1; //struct 关键字可以省略
       stu1.name = "张三";
       stu1.age = 18;
       stu1.score = 100;
       cout << "姓名:" << stu1.name << " 年龄:" << stu1.age  << " 分数:" << stu1.score << endl;
       //结构体变量创建方式2
       struct student stu2 = { "李四",19,60 };
       cout << "姓名:" << stu2.name << " 年龄:" << stu2.age  << " 分数:" << stu2.score << endl;
       stu3.name = "王五";
       stu3.age = 18;
      stu3.score = 80;
       cout << "姓名:" << stu3.name << " 年龄:" << stu3.age  << " 分数:" << stu3.score << endl;
       return 0;
    }

例1-46运行结果如图1-64所示。

图1-64 例1-46运行结果

总结1:定义结构体时的关键字是struct,不可省略。

总结2:创建结构体变量时,关键字struct可以省略。

总结3:结构体变量利用操作符“.”访问成员。

· 1.11.2 结构体作函数参数

可以把结构体作为函数参数,传参方式与其他类型的变量或指针类似,也可以使用例1-46中的方式来访问结构体变量。

具体使用方法见例1-47。

例1-47:结构体作函数参数。

    #include <iostream>
    #include <cstring>
    using namespace std;
    void printBook( struct Books book );
    // 声明一个结构体类型 Books 
    struct Books
    {
        char  title[50];
        char  author[50];
        char  subject[100];
        int   book_id;
    };
    int main( )
    {
        Books Book1;        // 定义结构体类型 Books 的变量 Book1
        Books Book2;        // 定义结构体类型 Books 的变量 Book2
         // Book1 详述
        strcpy( Book1.title, "C++教程");
        strcpy( Book1.author, "Runoob"); 
        strcpy( Book1.subject, "编程语言");
        Book1.book_id = 12345;
        // Book2 详述
        strcpy( Book2.title, "CSS 教程");
        strcpy( Book2.author, "Runoob");
       strcpy( Book2.subject, "前端技术");
        Book2.book_id = 12346;
        //输出 Book1 信息
        printBook( Book1 ); 
        //输出 Book2 信息
        printBook( Book2 );
        return 0;
    }
    void printBook( struct Books book )
    {
        cout << "书标题: " << book.title <<endl;
        cout << "书作者:" << book.author <<endl;
        cout << "书类目: " << book.subject <<endl;
        cout << "书ID:" << book.book_id <<endl;
    }

例1-47运行结果如图1-65所示。

图1-65 例1-47运行结果

· 1.11.3 结构体指针

定义结构体指针的方式与定义指向其他类型变量指针的方式相似,如下所示。

    struct Books *struct_pointer;

在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,需把“&”运算符放在结构名称的前面,如下所示。

    struct_pointer = &Book1;

为了使用结构体指针来访问结构中的成员,必须使用“->”,如下所示。

    struct_pointer->title;

使用结构体指针来重写上面的实例,有助于理解结构体指针的概念,具体见例1-48。

例1-48:结构体指针。

    #include <iostream>
    #include <cstring>
    using namespace std;
    void printBook( struct Books *book );
    struct Books
    {
      char  title[50];
       char  author[50];
       char  subject[100];
       int   book_id;
    }; 
    int main( )
    {
       Books Book1;        //定义结构体类型 Books 的变量 Book1
       Books Book2;        //定义结构体类型 Books 的变量 Book2
       // Book1 详述
       strcpy( Book1.title, "C++教程");
       strcpy( Book1.author, "Runoob"); 
       strcpy( Book1.subject, "编程语言");
       Book1.book_id = 12345;
       // Book2 详述
       strcpy( Book2.title, "CSS 教程");
       strcpy( Book2.author, "Runoob");
       strcpy( Book2.subject, "前端技术");
       Book2.book_id = 12346;
       //通过传 Book1 的地址来输出 Book1 信息
       printBook( &Book1 ); 
       //通过传 Book2 的地址来输出 Book2 信息
       printBook( &Book2 );
       return 0;
    }
    // 该函数以结构体指针作为参数
    void printBook( struct Books *book )
    {
       cout << "书标题:" << book->title <<endl;
       cout << "书作者:" << book->author <<endl;
       cout << "书类目:" << book->subject <<endl;
       cout << "书ID:" << book->book_id <<endl;
    }

例1-48运行结果如图1-66所示。

图1-66 例1-48运行结果

1.12 异常处理

异常是程序在运行期间产生的问题。C++异常是指在程序运行时发生的特殊情况,如尝试除以0的操作,如果不进行异常处理,程序就会崩溃。通过对用户在程序中的非法输入进行控制和提示,对不合适的处理进行异常处理,可防止程序崩溃。异常提供了一种转移程序控制权的方式,C++异常处理涉及3个关键字:try、catch、throw。

throw: 当问题出现时,程序会抛出一个异常。

catch: 在想要处理问题的地方,通过异常处理程序捕获异常。

try: try 块中的代码标识将被特定异常激活。它后面通常跟着一个或多个catch块。

如果有一个块抛出一个异常,捕获异常时会使用try和catch关键字。try块中放置可能抛出异常的代码,try块中的代码被称为保护代码。使用try-catch语句的语法如下。

    try
    {
       //保护代码
    }catch( ExceptionName e1 )
    {
       // catch 块
    }catch( ExceptionName e2 )
    {
       // catch 块
    }catch( ExceptionName eN )
    {
       // catch 块
    }

如果try块在不同的情境下抛出不同的异常,可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。

· 1.12.1 抛出异常

利用 throw 语句,可以在代码块中的任何地方抛出异常。throw 语句的操作对象可以是任意的表达式,表达式结果的类型决定了抛出异常的类型。

以下是尝试除以0时抛出异常的实例。

    double division(int a, int b)
    {
        if( b == 0 )
        {
             throw "Division by zero condition!";
        }
        return (a/b);
    }

· 1.12.2 捕获异常

catch块跟在try块后面,用于捕获异常。用户可以指定想要捕捉的异常类型,这是由catch 关键字后括号内的异常声明决定的。

    try
    {
        //保护代码
    }catch( ExceptionName e )
    {
       //处理 ExceptionName 异常的代码
    }

上面的代码会捕获一个类型为ExceptionName的异常。如果想让catch块能够处理try块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号,具体如下。

    try
    {
        //保护代码
    }catch(...)
    {
       //能处理任何异常的代码
    }

抛出一个除以0的异常,并在catch块中捕获该异常的代码见例1-49。

例1-49:捕获异常。

    #include <iostream>
    using namespace std;
    double division(int a, int b)
    {
        if( b == 0 )
        {
             throw "Division by zero condition!";
        }
        return (a/b);
    }
    int main ()
    {
        int x = 50;
        int y = 0;
        double z = 0;
        try {
           z = division(x, y);
           cout << z << endl;
        }catch (const char* msg) {
           cerr << msg << endl;//cerr表示输出到标准错误的ostream对象
        }
        return 0;
    }

由于抛出了一个类型为const char*(division()函数中)的异常,因此,当捕获该异常时,必须在catch块中使用const char*。

例1-49运行结果如图1-67所示。

图1-67 例1-49运行结果

在执行“try-throw-catch”语句块的过程中,程序运行到try块后,执行try块内的程序块。如果没有引起异常,那么跟在try块后的catch子句都不执行,程序从最后一个catch子句后面的语句继续执行下去;如果抛出异常,则暂停当前函数的执行,开始查找匹配的catch子句并执行。首先要检查throw是否在匹配的try块内,如果是,检查catch子句,看其中之一与抛出对象是否匹配。如果找到匹配的catch,就处理异常;如果找不到,就退出当前函数并释放局部对象,然后继续在调用函数中查找。在调用函数中,检查与该try块相关的catch子句。如果找到匹配的catch,就处理异常;如果找不到,则退出调用函数,然后继续在调用这个函数的函数中查找。沿着嵌套函数调用链继续向上寻找,直到为异常找到一个catch子句。只要找到能够处理异常的catch子句,就进入该catch子句,并在它的处理程序中继续执行。当catch结束时,跳转到该try块的最后一个catch子句之后的语句继续执行。下面通过例1-50进行详细说明。

例1-50:异常处理。

    #include<iostream>
    using namespace std;
    int j=0; 
    void fun(int test){
       if(test==0) throw test; //抛出整型异常
       if(test==1) throw 1.5;//抛出双精度浮点型异常
       if(test==2) throw "abc";//抛出字符型异常
       cout<<"fun调用正常结束 "<<j++<<endl;
    }
    void caller1(int test)
    {
       try{
           fun(test);
       } 
       catch(int) {
           cout<<"caller1捕获int "<<j++<<endl;//捕获异常
       }
       cout<<"caller1调用正常结束 "<<j++<<endl;//caller1正常结束时输出
    }
    void caller2(int test)
    {
       try{
           caller1(test); //检测异常时发生捕获异常
       } 
       catch(double){
           cout<<"caller2捕获double "<<j++<<endl;
       } 
       catch(...) {
           cout<<"caller2捕获所有未知异常 "<<j++<<endl; //捕获异常
       }
       cout<<"caller2调用正常结束 "<<j++<<endl;
    }
    int main(){
       for(int i=3;i>=0;i--)
           caller2(i);
       return 0;
    }

例1-50运行结果如图1-68所示。

图1-68 例1-50运行结果

第1次运行时,i=3,调用函数caller2(),函数caller2()调用函数caller1(),函数caller1()调用函数fun(3),此时fun()运行正常,输出结果如下。

    fun调用正常结束 0

然后依次返回到caller1()函数和 caller2()函数,输出结果如下。

    caller1调用正常结束 1
    caller2调用正常结束 2

第2次运行时,i=2,调用函数caller2(),函数caller2()调用函数caller1(),函数caller1()调用函数fun(2),此时fun()运行异常,抛出异常“abc”,即字符型异常。由于fun()函数没有try、catch等异常处理模块,这个异常抛给fun()的调用者caller1()函数,而caller1()函数因为没有处理字符型的异常,这个异常再次抛给调用者函数caller2(),函数caller2()通过catch(…)(“…”表示拦截所有异常)捕获“abc”这个字符型异常,输出结果如下。

    caller2捕获所有未知异常 3
    caller2调用正常结束 4

第3次运行时,i=1,与第2次类似,caller2()函数通过catch(double)捕获异常,输出结果如下。

    caller2捕获double 5
    caller2调用正常结束 6

第4次运行时,i=0,调用函数caller2(),函数caller2()调用函数caller1(),函数caller1()调用函数fun(0),此时fun运行异常,抛出异常test=0为整型异常。由于函数fun()没有try、catch等异常处理模块,这个异常抛给函数fun()的调用者caller1()函数,函数caller1()通过catch(int)处理这个异常,最后回到caller2()函数,最终输出结果如下。

    caller1捕获int 7
    caller1调用正常结束 8
    caller2调用正常结束 9

1.13 命名空间

在C++中,变量、函数以及类都是大量存在的,这些变量、函数以及类的名称都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染。比如,不同程序员可能会命名同一个变量或者函数名。为了解决变量和函数等的作用范围问题,C++引入了命名空间的概念,并增加了关键字namespace和using。在一个命名空间中可以定义一组变量和函数,这些变量和函数的作用范围一致,可以将这些变量和函数称为这个命名空间的成员。通过命名空间,可以在同一个文件中使用相同的变量名或函数名,只要它们属于不同的命名空间。另外,命名空间可以使代码操作具有相同名字但属于不同库的变量。

标准C++库中所包含的所有内容(包括常量、变量、结构、类以及函数等)都被定义在标准命名空间std(standard,标准)中了。

· 1.13.1 命名空间的定义

命名空间的定义使用关键字 namespace,后跟命名空间的名称,示例如下。

    namespace namespace_name {
        // 代码声明
    }

为了调用带有命名空间的函数或变量,需要在函数或变量前面加上命名空间的名称,示例如下。

    name::code; // code 可以是变量或函数

下面通过例1-51来看一下命名空间如何为变量或函数等实体定义范围。

例1-51:命名空间。

    #include <iostream>
    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;
    }

例1-51运行结果如图1-69所示。

图1-69 例1-51运行结果

· 1.13.2 使用命名空间

编写代码时,可以使用 using namespace 指令,这样在使用命名空间时就可以不用在函数或变量前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。具体见例1-52。

例1-52:使用命名空间。

    #include <iostream>
    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;
    }

例1-52运行结果如图1-70所示。

图1-70 例1-52运行结果

1.14 在统信UOS环境下安装Qt

在统信Unity Operating System(UOS)环境下安装Qt比较简单,在统信UOS的桌面右击并选择“在终端中打开”,打开UOS的命令行终端,在命令行终端输入以下命令即可完成Qt 5.11.3的安装。

    sudo apt-get install qt5-default qtcreator

输入命令后sudo(类似于Windows的添加/删除程序)自动从网络下载所需的包,例如开发工具Qt Creator、编译器qmake、帮助文档、开发样例,等等,下载中输入字母y来确认下载即可。

1.15 小结

本章通过52个实例介绍了C++的基础语法。通过本章,读者可了解C++的数据类型、变量的概念及C++应该如何定义变量。读者需要掌握“结构化程序设计”和“数组与字符串”这两节,重点理解、掌握函数与指针的思想和使用,理解为什么要使用函数和指针。本章作为C++的基础,其代码将贯穿全书。希望读者能认真学习本章的理论部分,并动手编写实例中的代码,加深学习印象。

1.16 习题

1.输入3个整数,将之按从小到大的顺序输出。

2.输出所有的“水仙花数”。水仙花数指一个3位数,其各位数字的立方和为它本身,如153 = 13 + 53 + 33,153即是水仙花数。

3.输入一个偶数x,输出两个素数且素数之和为x

4.输入一个数组x,对x进行从小到大排序并输出。

5.相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以3人一排、5人一排、7人一排地变换队形,而他每次只看一眼队伍的排尾就知道总人数了。输入3个非负整数abc,表示每种队形排尾的人数(a<3、b<5、c<7),输出总人数的最小值(或报告无解)。已知总人数不小于10,不超过100 。

6.有n盏灯,编号为1~n,第1个人把所有灯打开,第2个人按下所有编号为2的倍数的开关(这些灯将被关掉),第3个人按下所有编号为3的倍数的开关(其中关掉的灯将被打开,开着的灯将被关闭),依此类推。一共有k个人,问最后有哪些灯开着?输入nk,输出开着的灯的编号,其中kn≤100。

7.实现一个学生管理系统。学生有姓名,学号,语、数、英成绩等信息,使用一个数组来存储,需要有输出学生信息列表的函数和按成绩排名的函数。

相关图书

Rust游戏开发实战
Rust游戏开发实战
JUnit实战(第3版)
JUnit实战(第3版)
Kafka实战
Kafka实战
Rust实战
Rust实战
PyQt编程快速上手
PyQt编程快速上手
Elasticsearch数据搜索与分析实战
Elasticsearch数据搜索与分析实战

相关文章

相关课程