首页 > 技术文章 > 更新中。。。

wjq13752525588 2020-08-22 19:33 原文

C++预处理器和iostream文件

  下面简要介绍已一下需要知道的一些知识 。如果程序要使用C++输入或输出工具,请提供这养两行代码:

  #include <iostream>

  using namespace std;

  可使其他代码替换第二行,这里使用这行代码旨在简化该程序(如果编译器不受这几行代码,则说明他没有遵守C++98,使用它来编译实例时,将出现众多的其他问题)。为使程序正常工作,只需要知道这些。下面更深入的介绍一下这些内容。

  C++和C一样,也使用一个预编译处理器,该程序再进行 主编译之前对源文件进行处理(第一章介绍过,有些C++实现使用翻译器程序将C++程序转换成C程序。虽然翻译器也是一种预处理器,但这里不讨论这种预处理器,而只讨论这样的预处理器,即它处理名称以#开头的编译指令)。不必执行任何特殊的操作来调用该预处理器,他会在编译程序时自动运行。

  程序清单2.1使用了#include编译指令:

  #include  <iostream>  //  a PREPROCESSOR directive

  该编指令导致预处理器将iostream文件的内容添加到程序中。这是一种典型的预处理器操作:再源代码被编译之前,替换或添加文本。

  这提出了一个问题:问什么要将iostream文件的内容添加到程序中呢?答案涉及程序与外部世界之间的通信。iostream中的io指的是输入(进入程序的信息)和输出(从程序中发送出去的信息)。C++的输入\输出方案涉及iostream文件中的 多个定义。为了使用cout来显示消息,第一个程序需要这些定义。#include编译指令导致iostream文件的内容随源代码文件的内容一起被发送给编译器。实际上,iostream文件的内容将取代程序中的代码行#include <iostream> 。原始文件没有被修改,而是将源代码文件和iostream 组合成一个复合文件,编译的 下一阶段将使用该文件。

 注意:使用cin和cout进行输入和输出的程序必须包含文件iostream。

 

头文件名

  像 iostream这样的文件叫做包含文件(include file)——由于它们被包含再其他文件中;也叫头文件(header file)——由于它们被包含在文件的起始处。C++编译器自带了很多的头文件,每个头文件都支持一组特定的工具。C语言的传统是,头文件使用扩展名h,将其作为一种通过名称标识文件类型的简单方式 。例如,头文件math.h支持各种C语言数学函数C++用法变了。现在,对老式C的头文件保留了扩展名h(C++程序仍可以使用这种文件),而C++头文件则没有扩展名。有些C头文件被转换为C++头文件,这些文件被重新命名,去掉了扩展名h(使之成为C++风格的名称),并在文件名称前面加上前缀c(表明来自C语言)。例如,C++版本的math.h 为 cmath。有时C头文件的C版本和C++版本相同,而有时候新版本做了一些修改。对于纯粹的C++头文件(如iostream)来说,去掉 h不只是形式上的变化,没有h的头文件也可以包含名称空间。

  由于C使用的不同的二文件扩展名来表示不同文件类型,因此用一些特殊的扩展名(如.hpp 或.hxx)表示C++头文件是有道理的,ANSI/ISO委员会会也是这样认为。问题在于究竟是使用哪种扩展名,因此最终他们一致同意不使用任何扩展名。

命名空间

  如果使用iostream,而不是iostream.h,则应使用下面的命名空间编译指令来使iostream中的定义对程序可用:

  using namespace std;

  这叫做using编译指令。最简单的办法是,现在接受这个编译指令,以后再考虑它。但这里还是简要的介绍它,一面一头雾水。

  名称空间支持 是一项C++特性,旨在让您编写大型程序以及将多个厂商现有的代码组合起来的程序时更容易,它还有助于组织程序。一个潜在的问题是,可能使用两个已封装好的产品,而它们都包含一个名为wanda()的函数。这样,使用 wanda()函数时,编译器将不知道指的是哪个版本。名称空间让厂商能够将其产品封装在一个叫做名称空间的单元中,这样就可以用命名空间的名称来指出想使用哪个厂商的产品。

  因此,Microflop Industries 可以将其定义放到一个名为Microflop的命名空间中。这样,其wanda()函数的全称为Microflop::wanda();同样,Piscine公司的wanda()版本可以表示为Piscine::wanda()。这样,程序就可以使用命名空间来区分不同版本了:

  MicroflopL::wanda("go dancing?");  //use Microflop namespace version

  Piscine::wanda("a fish named Desire"); // use Piscine namespace version

  按照这种方式,类、函数和变量便是C++编译器的标准组件,它们现在都被放置在名称空间std中。仅当头文件没有扩展名h时,情况才是如此。这意味着在iostream中定义的用于输出的cout变量实际上是std::cout,而endl实际上是std::endl。因此,可以省略编译指令using ,以下述方式进行编码:

  std::cout << "Come up and C++ me some time.";

  std::cout << std::endl;

  然而,多数用户并不喜欢将引入名称空间之前的代码(使用iostream.h 和 cout )转换为名称空间代码(使用iostream 和 std::dout ),除非他们可以不费力气的完成这种转换。于是,using 编译指令应运而生。下面的一行代码表明,可以使用std名称空间中定义名称,而不必使用std::前缀:

  using namespace std;

  这个using 编译指令使得std名称空间中的所有名称都可用。这是一种偷懒的方法,在大型项目中是一个潜在的问题。更好的方法是,只使所需的名称可用,这可以通过使用using声明来实现:

  using std::cout;   //make cout available 

  using std::endl;   // make endl available

  using std::cin;     //make cin available

  用这些编译指令替换下述代码后,便可以使用cin和cout,而不必加上std::前缀:

  

 

C++语句

  C++程序是一组函数,而每个函数又是一组语句。C++有好几种语句。声明语句创建变量,赋值语句给该变量提供一个值。另外,该程序还演示了cout的新功能。

  #include <iostream>

  int main()

  {

    using namespace std;

    int carrots;

 

    carrots = 25;

    cout << " I have ";

    cout << carrots;

    cout << " carrots.s" ;

    cout << endl;

    carrots = carrots - 1;

    cout << "Crunch, crunch . Now I have " << carrots << " carrots. " <<endl;

    return 0;

  }

  空行将声明语句与程序的其他部分分开。这是c常用的方法,但在C++中不那么常见。下面是该程序的输出:

  I have  25 carrots.

  Crunch, crunch. Now I have 24 carrots.

  

声明语句和变量

  计算机是一种精确的,有条理的机器。要将信息项存储在计算机中,必须指出信息的存储位置和所需的内存空间。在C++中,完成这种任务的一种相对简便的方法,是使用声明语句来指出存储类型并提供位置标签。例如,程序清单彪悍==包含这样一条声明语句:

  int carrots;

  这条语句提供了两项信息:需要的内存以及该内存单元的名称。具体的说,这条语句指出程序需要足够的存储空间来存储一个整数,在C++中int表示整数。

 

 

3 处理数据

  面向对象编程(OPP)的本质是设计并扩展自己的数据类型。设计自己的数据类型就是让类型与数据匹配。如果正确做到了这一点,将会发现以后使用数据时会容易的多。然而,在创建自己的类型之前,必须了解C++内置的类型,因为这个类型是创建自己类型的基本组件。

  内置的C++类型分两组:基本类型和复合类型。基本类型,即整数和浮点数。这听起来似乎只有两个类型,但C++知道,没有任何一种整型和浮点型能够满足所有的编程要求,因此对于这两种数据,它提供了多种变体。

  当然,程序还需要一种标识存储数据的方法——使用变量。

  简单变量

    程序通常都需要存储信息,——如Google股票当前的价格、纽约市8月份的平均湿度、美宪法中使用最多的字母及其相对使用频率或猫王模仿者的数目。为把信息存储在计算机中,程序必须记录3个基本属性:

    信息将存储在哪里;要存储什么值;存储何种类型的信息。

    到目前为止,采取的策略都是声明一个变量。声明中使用的类型描述了信息的类型和变量名(使用符号来表示其值)。例如,假设实验室首席助理lgor使用了下面的语句:

    这些语句告诉程序,它正在存储整数,并使用名称braincount来标识该整数的值(这里为5)。实际上,程序将找到一块能够存储整数的内存,将该内存的单元标记为braincount,并将5复制到该内存单元中;然后,您可在程序中是同braincount来访问该内存单元。这些语句没有告诉您,这个值将存储在内存的是么位置,但程序确实记录了这种信息。实际上,可以使用&运算符来检索braincount的内存地址。

  变量名

  C++提倡使用有一定含义的变量名。如果变量表示差旅费,应将其命名为cost_of_trip或costOfTrip,而不要将其命名为x或cot。必须遵循几种简单的C++命名规则。

  在名称中只能使用字母字符、数字和下划线(_)

  名称的第一个字符不能是数字

  区分大小写字符与小写字符

  不能将C++关键字用作名称

  以两个下划线打头或者以下划线和大写字母打头的名称被保留给实现(编译器及其使用的资源)使用。以一个下划线开头的名称被保留给实现,用作全局标识符。

  C++对于名称的长度没有限制,名称中所有的字符都有意义,但有些平台有长度限制。

  倒数第二点与前面几点有些不同,因为使用像_time_stop或_Donut这样的名称不会导致编译器错误,而会导致行为的不确定性。换句话说,不知道什么结果将是什么。不出现编译器错误的原因是,这样的名称不是非法的,但要留给实现使用。全局名称指的是名称被声明的位置。最后一点使得C++与ANSI (C99)标准有所区别,后者只保证名称中的前63个字符有意义(在ANSI 中,前63个字符相同的名称被认为是相同的,即使第64个字符不同)。

  如果想用两个或更多的单词组成一个名称,同城的做法是用下划线字符将单词分开,如my_onions;或者从第二个单词开始将每个单词的第一个字母大写,如myEyeTooth。(C程序员倾向于按C语言的方式是用下划线,而Pascal程序员喜欢采用大写的方式。)这两种形式都很容易将单词区分开,如carDrip和cardRip或boat_sport和boats_port。

  命名方案

  变量命名方案和函数命名方案一样,也有很多话题可供讨论。确实,该主题会引发一些尖锐的反对意见。同样,和函数名称一样,只要变量名合法,C++编译器就不会介意,但是一直,精确的个人命名约定是很有帮助的。

  与函数名一样,大写变量命名中也是一个关键问题(参见第二章的注释“命名约定”),但很多程序员可能会在变量名称中加入其他的信息,即描述变量类型或内容或内容的前缀。例如,可以将整型变量myWeight命名为nMyWeight,其中,前缀n用来表示整数值,在阅读代码或变量定不是十分清楚的情况下,前缀很有用。另外,这个变量也可以叫做intMyWeight,这将更明确,而且更容易理解,不过他多了几个字母(对于很多程序员来说,这是非常讨厌的事)。常以这样的方式使用的其他前缀有:str或s(标识一空字符结束的字符串)、b(表示布尔值)、p(表示指针)、和c(表示单个字符)。

  随着对C++的逐步了解,将发现很多有关前缀命名风格的示例(包括漂亮的m_lpctstr前缀——这是一个类成员值,其中包含了指向常量的长指针和以空字符结尾的字符串),还有其他更奇异、更违反直觉的风格,采不采用这些风格,完全取决于程序员。在C++所有主主观的风格中,一致性和精度是最重要的。请根据自己的需要、喜好和个人风格来选择变量名)。

 

  整型

  整数就是没有小数部分的数字,如2、98、-5286和0。整数有很多,如果将无限大的整数看作很大,则不可能用有限的计算机内存来表示所有的整数。因此,语言只能表示所有整数的一个子集。有些语言只提供一种整型(一种类型满足所有要求!),而C++则提供好几种,这样便能够根据程序的具体要求选择最合适的整型。

  不同C++整型使用不同的内存来存储整数。使用的内存量越大,可以表示的整数值范围越大。另外,有的类型(符号类型)可表示正值和负值,而有的类型(无符号类型)不能表示负值。术语宽度(width)

用于描述存储整数是使用的内存量。使用的内存越多,则越宽。C++的基本整型(按宽度递增的顺序排列)分别是char、short、int、long、和C++11新增的longlong,其中每种类型都有符号版本和无符号版本,因此总共有10种类型可供选择。由于char类型有一些特殊属性(它最常用来表示字符,而不是数字)

  

 

函数

  由于函数用于创建C++程序的模块,对C++的OOP定义至关重要,因此必须熟悉它。函数的某些方面属于高级主题。然而,现在了解函数的一些基本特征,将使得在以后函数学习中更加得心应手。

  C++函数分两种:有返回值的和没有返回值的。在标准C++函数库中可以找到这两类函数简单的例子,可以自己创建这两种类型的函数。下面首先来看一个有返回值的库函数,然后介绍如何编写简单的函数。

  使用有返回值的函数将生成一个值,而这个值可赋给变量或在其他表达式中使用。例如,标准C/C++库包含一个名为sqrt()的函数,他返回平方根。假设要计算6.25的平方根,并将这个值赋给变量X,则可以在程序中使用下边的语句:

  x = sqrt(6.25); //returns the value 2.5 and assigns it to x

  表达式sqrt(6.25)将调用sqrt(6.25)被称为函数调用,被调用的函数叫做被调用函数,包含函数调用的函数叫做调用函数。

  圆括号中的值(这里为6.25)是发送给函数的信息,这被称为传递给函数。以这种方式发送给函数的值叫做参数。函数sqrt()得到的结果为2.5,并将这个值发送给调用函数;发送回去的值被叫做函数的返回值。可以这么认为,函数执行完毕后,语句中的函数调用部分将被替换为返回的值。因此,这个例子将返回值赋给变量x。简而言之,参数是发送给函数的信息,返回值是从函数中发送回去的值。

  情况基本就是这样,只是在使用函数之前,C++编译器必须知道函数的参数类型和返回值类型。也就是说,函数是返回整数、字符、小数、布尔类型还是别的什么东西?如果缺少这些信息,编辑器将不知道释放返回值。C++提供这种信息的方式是使用函数原型语句。

  注意:C++城西应当为程序中使用的每个函数提供原型。

  函数原型之于函数就像变量声明之于变量——指出涉及的类型。例如,C++库将sqrt()函数定义成将一个带小数部分的数字(如6.25)作为参数,并返回一个相同类型的数字。有些语言将这种数字成为实数,但是C++将这种类型称为double。sqrt()函数原型像这样:

  double sqrt(double);    //function prototype

  第一个double意味着sqrt()将返回一个double值。括号中的double意味着sqrt()需要一个double参数因此该原型对sqrt()的描述和下面代码中使用的函数相同:

  double x;   //declare x as a type double variable

  x = sqrt(6.25);

  原型结尾的分号表明它是一条语句,这使得它是一个原型,而不是函数头。如果省略分号,编译器将把这行代码解释为函数头,并要求接着提供定义该函数的函数体。

  在程序中使用sqrt()时,也必须提供原型。可以用两种方法来实现:

  (1)在源代文件中输入函数原型;

  (2)包含头文件cmath(老系统为math.h),其中定义了原型。

  第二种方法更好,因为头文件更有可能使原型正确。对于C++库中的每个函数,都在一个或多个头文件中提供了其原型。例如,sqrt()函数的说明将指出,应使用cmath头文件(同样,可能必须使用老式的头文件math.h,它可用于C和C++程序中)。

  不要混淆函数原型和函数定义。可以看出,圆形只描述函数接口。也就是说,它描述的是发送给函数的信息和返回的信息。而定义中包含了函数的代码,如计算的平方根的代码。C和C++将库函数的这两项特性(原型和定义)分开了。库文件中包含了函数的代码,而头文件中则包含了原型。

  应在首次使用函数之前提供其原型。通常的做法是把原型放到main()函数定义的前面。

  // sqrt.cpp -- using the sqrt() function

  #include <iostream>

  #inslude <cmath>  // or math.h

  int main()

  {  

    using namespace std;

    double area;

    cout << "Enter the floor area, in square feet, of your home: "; 

    cin >> ares;

    double side;

    side = sqrt(area);

    cout << "That's the equivalent of a square " << side

      << " feet to the side." << endl;

    cout << "How fascinating!" << endl;

    return 0;

  }

  注意:如果使用的是老式编译器,则必须在程序中使用#include<math.h>,而不是#include<cmath>。

                          使用库函数

  C++库函数存储在库文件中。编译器编译程序是,它必须在库文件中搜索您使用的函数。至于自动搜索哪些库文件,将因编译器而异。如果运行以上程序,得到一条消息,指出_sqrt是一个没有定义的外部函数(似乎应当避免),则可能是由于编译器不能自动搜索数学库(编译器倾向于给函数名添加下划线前缀——提示它们对程序具有最后的发言权)。如果在UNIX实现中遇到这样的消息,可能需要在命令行结尾使用-lm选项:

  CC sqrt.C -lm

  在Linux系统中,有些版本的Gnu编译器与此类似:

  g++ sqrt.C -lm

  只包含cmath头文件可以提供原型,但不一定会导致编译器搜索正确的库文件。

  下面是该程序的运行情况:

  Enter the floor area,in square feet, of you home: 1536

  That's the equivalent of a square 39.1918 feet to the side.

  How fascinating!

  由于sqrt()处理的是double值,因此这里将变量声明为这种类型。声明double变量的句法与声明 int 变量相同:

  type-name variable-name;

  double类型使得变量area和side能够存储带小数的值。

4、复合类型

  假设您开发了一个名叫User-Hostile的计算机游戏,玩家需要用只会来应对一个神秘、险恶的计算机界面。现在,必须编写一个程序来跟踪5年来游戏每月的销量,或者希望盘点一下与黑客英雄累计积累的较量回合。您很快发现,需要一些比C++的简单基本类型更复杂东西,才能满足这些数据的要求,C++也提供了这样的东西——复合类型。这种类型是基于基本类型和浮点类型创建的。影响最为深远的复合类型是类,它是学习的OOP的堡垒。然而,C++还支持几种更普通的复合类型,他们都来自于C语言。例如,数组可以存储多个同类型的值。一种特殊的数组可以存储字符串(一系列字符)。 结构可以存储多个不同类型的值。而指针则是一种将数据所处位置告诉计算机的变量。

  数组

  数组(array)是一种数据格式,能够存储多个同类型的值。例如,数组可以存储60个int类型的值(这些值表示游戏5年来的销售量)、12个short值(这些表示每个月的天数)或365个float值(这些值指出一年中每天在食物方面的开销)。每个值都从存储在一个独立的数组元素中,计算机在内存中一次存储数组的各个元素。

  要创建数组,可使用声明语句。数组声明应指出以下三点:

  1》存储在每个元素中的值的类型

  2》数组名

  3》数组中的元素数

  在C++中,可以通过修改简单变量的声明,添加中括号(其中包含元素数目)来完成数组声明。例如,下面的声明创建一个名为months的数组,该数组有12个元素,每个元素都可以存储一个short类型的值:

  short months[12];

  事实上,可以将数组中的每个元素看作是一个简单变量。

  声明数组的通用格式如下:

  typeName arrayName[arraysize];

  表达式arraysize指定元素数目,它必须是整型常数或const值,也可以是常量表达式(如 8 * sizeof(int) ),即其中所有的值在编译时都是已知的。具体地说,arraySize不能是变量,变量的值是在程序运行是设置的。

  作为复合类型的数组

  数组之所以被称为复合类型,是因为它是使用其他类型来创建的(C语言使用术语“派生类型”,但由于C++对类关系使用术语“派生”,所以她必须船舰一个新术语)。不能仅仅将某种东西声明为数组,它必须是特定类型的数组。没有通用的数组类型,但存在很多特定的数组类型,如char数组或long数组。例如,请看下面的声明:

  float loans[20];

  loans的类型不是“数组”,而是“float数组”。这强调了loans数组是使用float类型创建的。

  数组的很多用途都是基于这样一个事实:可以单独访问数组元素。方法是使用下标或者索引来对元素进行编号。C++数组从0开始编号(这没有商量的余地,必须从0开始。Pascal和BASIC用户必须调整习惯)。C++使用带索引的方括号表示法来指定数组元素。例如,months[0]是months数组的第一个元素,months[11]是最后一个元素。注意,最后一个元素的索引比数组长度小1。因此,数组声明能够使用一个声明创建大量的变量,然后便可以用索引来表示和访问各个元素。

  有效下标值的重要性

  编译器不会检查使用的下标是否有效,例如,如果将一个值赋给不存在的元素months[10],编译器名不会指出错误,但是程序运行后,这种赋值可能引发问题,它可能破环数据或者代码,也可能导致程序终止。所以必须保证程序只使用有效的下标值。

  程序清单4.1中的马铃薯分析程序说明了数组的一些属性,包括声明数组、给数组元素赋值以及初始化数组。

  程序清单4.1 arrayone.cpp

  

 

  下面是该程序的输出:

  Total yams = 21

  The package with 8 yams costs 30 cents per yam.

  The total yam expense is 410 cents.

  

  Size of yams array = 12 bytes.

  Size of one element = 4bytes.

  4.1.1 程序说明

  该程序首先创建一个名为yams的包含3个元素的数组。由于yams有3个元素,他们的编号为0~2,因此arrayone.cpp使用索引0~2分别给这单个元素赋值。yams的每个元素都是int,都有int类型的权力和特权,因此arrayone.cpp能够将赋值给元素,将元素相加和相乘,并显示它们。

  程序给yams的元素赋值时,绕了一个大弯。C++允许在声明语句中初始化数组元素。程序4.1使用这种捷径来给yamsconst数组赋值:

  int yamcosts[3] = {20, 30 ,5};

  只需提供一个逗号分隔的值列表(初始化列表),并将它们用花括号括起来即可。列表中的空格是可选的。如果没有初始化函数中定义的数组,则其元素值将是不确定的,这意味着元素的值为以前驻留在该内存单元中的值。

  接下来,成熟使用数组值机型一些计算。程序的这部分由于包含了下标和括号,所以看去有些混乱。等第5张介绍for循环,它可以提供一种强大的方法处理数组,因而不用显式地书写每个索引。同是,我们仍然坚持使用小型数组。

  您可能还记的,sizeof运算符返回类型或数据对象的长度(单位为字节)。注意,如果将sizeof运算符用于数组名,得到的将是整个数组中的字节数。但如果将sizeof用于数组元素,则得到的将是元素的长度(单位为字节)。这表明yams是一个数组,而yams[1]是一个int变量。

  4.1.2 数组的初始化规则

  C++有几条关于初始化数组的规则,它们限制了初始化的时刻,决定了数组的元素数目与初始化器中值的数目不相同是发生的情况。我们来看看这些规则。 

  只有在定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组 

推荐阅读