C--编程零基础入门指南-二-

171 阅读46分钟

C# 编程零基础入门指南(二)

原文:C# Programming for Absolute Beginners

协议:CC BY-NC-SA 4.0

八、输入

到目前为止,您的所有程序都在操作数据(数字、文本等),这些数据要么直接固定在源代码中,要么来自操作系统(日期、随机数等)。通常,程序从用户那里获取数据,这是你将在本章学到的。

文本输入

你将从最简单的例子开始学习输入。

工作

您将编写一个程序,接受用户的单行文本,并立即将输入的文本重复输出(见图 8-1 )。

img/458464_2_En_8_Fig1_HTML.jpg

图 8-1

完整的程序

解决办法

代码如下:

static void Main(string[] args)
{
    // Reading single line of text (until user presses Enter key)
    string input = Console.ReadLine();

    // Outputting the input
    Console.WriteLine(input);

    // Waiting for Enter
    Console.ReadLine();
}

当您使用 F5 键启动程序时,您会看到一个空屏幕。输入一个句子,并使用回车键将其发送到程序。

优质投入

在前面的程序中,用户可能不知道该做什么。你还没有告诉用户该做什么。在本练习中,您将改进输入过程。

工作

你将修改前面的程序,给用户一个提示,告诉他们应该做什么(见图 8-2 )。

img/458464_2_En_8_Fig2_HTML.jpg

图 8-2

改进的程序

解决办法

代码如下:

static void Main(string[] args)
{
    // Hinting user what we want from her
    Console.Write("Enter a sentence (and press Enter): ");

    // Reading line of text
    string input = Console.ReadLine();

    // Repeating to the output
    Console.WriteLine("You have entered: " + input);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

Console.Write不会将光标移动到下一行,这与Console.WriteLine相反,到目前为止您一直在使用Console.WriteLine

数字输入

在前面的练习中,您参与了来自用户的文本信息的输入。现在你将转到数字上,它们同样重要。

工作

您将编写一个程序,从用户那里获取一个数字,将其存储在一个数值变量中,最后向用户重复该数字(见图 8-3 )。

img/458464_2_En_8_Fig3_HTML.jpg

图 8-3

从屏幕上读取一个数字

解决办法

总是读取文本,即使它的含义是一个数字。如果你想保存一个实数(例如,int类型的值),你必须使用Convert.ToInt32调用来制造它。

static void Main(string[] args)
{
    // Prompting the user
    Console.Write("How old are you? ");

    // Reading line of text
    string input = Console.ReadLine();

    // CONVERTING TO NUMBER (of entered text)
    int enteredNumber = Convert.ToInt32(input);

    // Output of entered number
    Console.WriteLine("Your age: " + enteredNumber);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

严格地说,你实际上还不需要一个实数,因为你没有对这些数进行任何计算。但是,这将在下一个练习中改变。在这里,您探索了最简单形式的数字输入。

用输入的数字计算

现在,您将使用用户输入的值进行第一次计算。

工作

您将编写一个程序,接受用户的出生年份,然后计算他们的年龄(见图 8-4 )。

img/458464_2_En_8_Fig4_HTML.jpg

图 8-4

计算年龄

解决办法

以下是解决方案:

static void Main(string[] args)
{
    // Prompting the user
    Console.Write("Enter year of your birth: ");

    // Reading line of text
    string input = Console.ReadLine();

    // CONVERING TO NUMBER (of entered text)
    int yearOfBirth = Convert.ToInt32(input);

    // Finding this year
    DateTime today = DateTime.Today;
    int thisYear = today.Year;

    // Calculating age
    int age = thisYear - yearOfBirth;

    // Outputting the result
    Console.WriteLine("This year you are/will be: " + age);

    // Waiting for Enter
    Console.ReadLine();
}

还有十个

让我们继续计算。

工作

您将编写一个程序,从用户那里接受一个数字。之后,它显示一个比输入的数字大十的数字(见图 8-5 )。

img/458464_2_En_8_Fig5_HTML.jpg

图 8-5

给一个数加 10

解决办法

代码如下:

static void Main(string[] args)
{
    // Number input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Calculating
    int result = number + 10;

    // Displaying the result
    Console.WriteLine("Number greater by ten: " + result);

    // Waiting for Enter
    Console.ReadLine();
}

添加

现在,您将进一步执行这一步骤,并使用来自用户的两个数字进行计算。

工作

您将编写一个程序,将用户输入的两个数字相加(见图 8-6 )。

img/458464_2_En_8_Fig6_HTML.jpg

图 8-6

将两个数相加

解决办法

代码如下:

static void Main(string[] args)
{
    // Input of 1\. number
    Console.Write("Enter 1\. number: ");
    string input1 = Console.ReadLine();
    int number1 = Convert.ToInt32(input1);

    // Input of 2\. number
    Console.Write("Enter 2\. number: ");
    string input2 = Console.ReadLine();
    int number2 = Convert.ToInt32(input2);

    // Calculating
    int result = number1 + number2;

    // Result output
    Console.WriteLine("Sum of entered numbers is: " + result);

    // Waiting for Enter
    Console.ReadLine();
}

输入不正确

在以前有数字的程序中,如果用户输入了数字以外的东西,程序会以一个运行时错误终止。然而,生产程序不应该这样。现在,您将学习如何处理运行时错误。

工作

在本练习中,您将修改之前的程序,使其正确处理来自用户的非数字输入(参见图 8-7 )。

img/458464_2_En_8_Fig7_HTML.jpg

图 8-7

为错误提供反馈

解决办法

让您的上一个项目保持打开状态,或者如果您已经关闭了它,请再次打开它。在接下来的内容中,您将编辑项目的Program.cs源代码;具体来说,您将在适当的位置插入一个try-catch构造。

用鼠标选中Main的整个内部,不包括最后一条语句(等待回车),如图 8-8 所示。然后,右键单击所选块中的任意位置,并从上下文菜单中选择“代码段”,然后选择“包围”。

img/458464_2_En_8_Fig8_HTML.jpg

图 8-8

选择环绕方式

在弹出的小窗格中,选择 Try(见图 8-9 )。

img/458464_2_En_8_Fig9_HTML.jpg

图 8-9

选择尝试

发生了什么

发生了什么事?Visual Studio 将选中的行包装到try块中,该块由单词try和一对花括号组成。它还在try块之后插入了一个catch块,其中包括单词catch和一对花括号。

捕捉部分的内部

删除catch块中的语句throw,并输入以下语句:

Console.WriteLine("Incorrect input - cannot calculate");

完全解

以下是完整的解决方案:

static void Main(string[] args)
{
    try
    {
        // Input of 1\. number
        Console.Write("Enter 1\. number: ");
        string input1 = Console.ReadLine();
        int number1 = Convert.ToInt32(input1);

        // Input of 2\. number
        Console.Write("Enter 2\. number: ");
        string input2 = Console.ReadLine();
        int number2 = Convert.ToInt32(input2);

        // Calculating
        int result = number1 + number2;

        // Result output
        Console.WriteLine("Sum of entered numbers is: " + result);
    }
    catch (Exception)
    {
        Console.WriteLine("Incorrect input - cannot calculate");
    }

    // Waiting for Enter
    Console.ReadLine();
}

测试

现在你可以测试你的程序的数字输入和无意义输入。

说明

try块中的语句以一种“试验模式”执行:

  • 当它们全部成功时,try块中的执行正常进行,之后跳过catch块。

  • 当一条语句失败时,跳过剩余的try块,而执行catch块中的语句。

摘要

在这一章中,你进入了一个新的编程技能水平。到目前为止,您只考虑了程序的输出。这里,您开始处理来自用户的输入,首先是文本输入,然后是数字输入。

具体来说,您学到了以下内容:

  • 使用Console.ReadLine方法调用从用户那里获得文本输入。

  • 在请求输入前向用户显示提示。为此,您使用了Console.Write方法,它与它的姐妹Console.WriteLine不同,它不终止一行。

  • 使用Convert.ToInt32方法将数字的文本输入转换成实际的数字表示,然后用它进行各种计算。

在最后一个练习中,您考虑了运行时错误的重要情况,比如非数字输入。您学会了使用try-catch构造来处理它们。该结构由两部分组成:

  • try块包围着“试验中”执行的语句如果一切顺利,try块不会改变任何东西,在它完成之后,程序的执行会在整个try-catch构造之后立即继续。

  • try程序块处理过程中出现错误时,catch程序块包围专门执行的语句。在有catch块的情况下,try块中失败的语句不会导致运行时错误和程序终止。相反,错误被“捕获”,并启动指定的替代操作。

九、数字

在前一章中,您学习了一般的输入,特别是数字输入。您还对用户输入的数字进行了一些简单的计算。在这一章中,你将更详细地了解数字。毕竟电脑之所以叫电脑,是因为计算频繁!

十进制输入

您将从从用户处读取一个十进制数的任务开始。

工作

您将编写一个程序,从用户那里接受一个十进制数,并立即在屏幕上重复它(见图 9-1 )。

img/458464_2_En_9_Fig1_HTML.jpg

图 9-1

最终方案

解决办法

整数和小数的输入有两个区别:

  • 您可以通过调用Convert.ToDouble方法将文本输入转换成相应的数字。

  • 要存储转换后的数字,可以使用类型为double的变量。

代码如下:

static void Main(string[] args)
{
    // Decimal input
    Console.Write("Enter a decimal number: ");
    string input = Console.ReadLine();
    double decimalNumber = Convert.ToDouble(input);

    // Repeating entered number to the output
    Console.WriteLine("You have entered number " + decimalNumber);

    // Waiting for Enter
    Console.ReadLine();
}

本地化数字输入

在上一个练习中,用户根据 Windows 语言设置输入小数点分隔符。这意味着英语中的小数点。然而,它在其他语言中可能有其他的意思。例如,在捷克语中,逗号用作小数点分隔符。

在当前的练习中,我将向您展示如何在特定的本地化环境中强制输入数字,而不管 Windows 设置如何。您在本书的前面做了一个关于本地化输出的类似任务;现在你要专注于输入。

工作

任务是写一个程序,读取一个十进制数,有两种固定的语言设置,美国和捷克。

解决办法

要处理特定的本地化,请使用CultureInfo对象。另外,请不要忘记在源代码的顶部插入using System.Globalization;行。

代码如下:

static void Main(string[] args)
{
    // AMERICAN
    CultureInfo american = new CultureInfo("en-US");
    try
    {
        // Input
        Console.Write("Enter American decimal number: ");
        string input = Console.ReadLine();
        double number = Convert.ToDouble(input, american);

        // Output
        Console.WriteLine("You have entered " + number);
    }
    catch (Exception)
    {
        // Error message
        Console.WriteLine("Incorrect input");
    }

    // CZECH
    CultureInfo czech = new CultureInfo("cs-CZ");
    try
    {
        // Input
        Console.WriteLine();
        Console.Write("Enter Czech decimal number: ");
        string input = Console.ReadLine();
        double number = Convert.ToDouble(input, czech);

        // Output
        Console.WriteLine("You have entered " + number);
    }
    catch (Exception)
    {
        // Error message
        Console.WriteLine("Incorrect input");
    }

    // Waiting for Enter
    Console.ReadLine();
}

测试和结论

以下部分介绍了这是如何工作的。

用小数点测试

运行您的程序,输入一个带小数点的数字两次(见图 9-2 )。

img/458464_2_En_9_Fig2_HTML.jpg

图 9-2

输入两个带小数点的数字

当使用美国本地化时,程序接受点作为小数点分隔符。同时,当使用捷克语本地化时,它拒绝小数点,因为点在捷克语中不是有效的小数点分隔符。

用十进制逗号测试

再次运行你的程序,这次输入一个带小数点的数字两次(见图 9-3 )。

img/458464_2_En_9_Fig3_HTML.jpg

图 9-3

用逗号输入一个数字两次

现在程序接受十进制逗号作为捷克语中的有效分隔符。

当使用美国本地化时,程序看不到任何十进制数。它只是忽略逗号,将用户输入转换成一个整数!

进一步的结论

在本书中,我用数字中的小数点显示输出。这是因为我没有在输出语句中指定任何本地化,并且我的 Windows 设置当前被设置为美国英语。两个测试都表明,如果你不够小心,十进制输入可能会出卖你。只是提醒你,如果你直接在你的 C# 源代码中输入一个十进制数,无论你的设置如何,你都应该使用小数点。

基本算术

在这一章中,您将与数字打交道,因此这是执行所有四种基本算术运算的好时机。

工作

您将编写一个程序,从用户那里接受两个十进制数,并显示它们的加、减、乘、除的结果(见图 9-4 )。

img/458464_2_En_9_Fig4_HTML.jpg

图 9-4

做基本算术

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter first number: ");
    string input1 = Console.ReadLine();
    double number1 = Convert.ToDouble(input1);

    Console.Write("Enter second number: ");
    string input2 = Console.ReadLine();
    double number2 = Convert.ToDouble(input2);

    // Calculations
    double sum = number1 + number2;
    double difference = number1 - number2;
    double product = number1 * number2;
    double quotient = number1 / number2;

    // Output
    Console.WriteLine("Sum is " + sum);
    Console.WriteLine("Difference is " + difference);
    Console.WriteLine("Product is " + product);
    Console.WriteLine("Quotient is " + quotient);

    // Waiting for Enter
    Console.ReadLine();
}

数学函数

当您进行工程或财务计算时,您通常需要比上一个练习中显示的四个基本操作更复杂的操作。现在您将看到如何使用内置的(预定义的)数学函数来执行复杂的运算。

工作

为了让您体验一下可用的数学函数,您将在该任务中计算输入数字的正弦和平方根(参见图 9-5 )。

img/458464_2_En_9_Fig5_HTML.jpg

图 9-5

计算正弦和平方根

解决办法

代码如下:

static void Main(string[] args)
{
    // Input of angle
    Console.Write("Enter an angle in degrees: ");
    string input = Console.ReadLine();
    double angleInDegrees = Convert.ToDouble(input);

    // Calculation and output of sine value
    double angleInRadians = angleInDegrees * Math.PI / 180;
    double result = Math.Sin(angleInRadians);
    Console.WriteLine("Sine of the angle is: " + result);

    // Input of a positive number
    Console.WriteLine();
    Console.Write("Enter a positive number: ");
    input = Console.ReadLine();
    double number = Convert.ToDouble(input);

    // Calculation and output of square root
    Console.WriteLine("Square root of the number is: " + Math.Sqrt(number));

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 要计算数学函数的值,可以使用Math对象;它包含许多有用的函数,不仅仅是上面显示的那些。

  • Sin功能要求以弧度指定角度。如果您的输入是以度为单位的,这是通常的情况,那么您需要进行转换。

  • 有了第二个输入(一个数字),你就“回收”了之前已经使用过的变量input;由于不再需要存储值,您第二次使用了它。但是,这意味着您不必再次声明该变量。

  • 你不必“回收”变量;如今,变量不是宝贵的资源。但是如果你想的话你可以,这就是我展示给你的。

  • 与第一个计算相反,您没有将计算出的平方根存储到任何变量中。你直接把计算写进了输出语句(WriteLine)。

  • 如果用户输入一个负数,它的平方根无法计算,结果变成NaN(意思是“不是一个数”)。

整数除法

令人惊讶的是,在编程时,你经常需要使用整数除法,也就是带余数的除法。例如,33 除以 7 通常是 4.71428…或者是 4 加余数 5。

在各种计算平台上,你会做不同于“正常”除法的整数除法。不幸的是,在 C# 中,两种类型使用相同的运算符,斜杠(/)。它是这样工作的:

  • 如果在两个int类型的值之间加一个斜杠,这个斜杠执行整数除法。

  • 如果两个值中至少有一个是double类型,斜杠执行“正常”除法。

这种行为可能是难看的、难以发现的错误的来源。这个行为已经有 45 年的历史了,起源于 C 语言被创造出来的时候;不幸的是,一些较新的语言,如 C#,继承了这种行为。注意这一点,在使用斜线时要小心。

工作

在本练习中,您将探索用户输入的两个数字的“正常”除法和整数除法(参见图 9-6 )。

img/458464_2_En_9_Fig6_HTML.jpg

图 9-6

探索“正常”和整数除法

解决办法

下面是代码:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter 1\. whole number (dividend): ");
    string input1 = Console.ReadLine();
    int number1 = Convert.ToInt32(input1);

    Console.Write("Enter 2\. whole number (divisor): ");
    string input2 = Console.ReadLine();
    int number2 = Convert.ToInt32(input2);

    // Integer calculations
    int integerQuotient = number1 / number2;
    int remainder = number1 % number2;

    // "Normal" calculations
    double number1double = number1;
    double number2double = number2;
    double normalQuotient = number1double / number2double;

    // Alternatively
    double normalQuotientAlternatively = (double)number1 / (double)number2;

    // Outputs
    Console.WriteLine("-----------------");
    Console.WriteLine("Integer quotient: " + integerQuotient +
        " with remainder " + remainder);
    Console.WriteLine("\"Normal\" quotient : " + normalQuotient);
    Console.WriteLine("\"Normal\" quotient (alternatively): " + normalQuotientAlternatively);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 要计算余数,可以使用%运算符(百分号)。

  • 我已经向您展示了两种将输入的值强制为double s 以实现“正常”除法的方法。

    • 类型为double的变量赋值。

    • 型投double;您可以在括号中加上目标类型的值。

摘要

在这一章中,你更详细地研究了数字。你已经知道在计算中整数和小数的区别,你知道如何阅读整数;在这一章中,你学习了如何阅读小数。您还发现,阅读十进制数字对语言很敏感,如果不小心,可能会导致令人惊讶的结果。如果不指定要使用的语言,将使用 Windows 语言设置读取数字。具体来说,您学习了以下内容:

  • 使用Convert.ToDouble方法将文本用户输入转换成实际的十进制数

  • 将转换后的值存储在double类型的变量中

  • 使用作为第二个参数传递给转换方法的CultureInfo对象实施语言设置

此外,您还学习了如何使用运算符+、-、*和/进行基本运算,以及如何使用(static) Math对象的内置数学函数进行更复杂的运算。

最后,您探索了整数除法及其与“普通”除法的比较,并了解了斜线运算符的一些复杂行为,它执行以下操作:

  • 与两个整数一起使用时的整数除法

  • 至少有一个数字是小数时的“正常”除法

您了解了如何对整数进行“正常”除法运算:

  • 要么在计算前将它们赋给double类型的变量

  • 在计算中对它们进行类型转换

十、经济计算

在这一章中,你将学习如何数钱。这很简单,但是你需要运用一些常识。

货币兑换

执行简单的经济计算通常意味着进行货币转换,这将在本节中尝试。

工作

接受欧元金额和欧元汇率后,您将把金额转换成美元(见图 10-1 )。

img/458464_2_En_10_Fig1_HTML.jpg

图 10-1

转换成美元

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter amount in euros: ");
    string inputEuros = Console.ReadLine();
    double amountEuros = Convert.ToDouble(inputEuros);

    Console.Write("Enter euro exchange rate (how many dollars per 1 euro): ");
    string inputExchangeRate = Console.ReadLine();
    double euroEchangeRate = Convert.ToDouble(inputExchangeRate);

    // Calculation
    double amountDollars = amountEuros * euroEchangeRate;

    // Output
    Console.WriteLine();
    Console.WriteLine("Amount in dollars: " + amountDollars);

    // Waiting for Enter
    Console.ReadLine();
}

总价

在本练习中,您将计算订单的总价。

工作

假设你的一个顾客买了几样东西,其中一些可能买了很多次。你需要计算包括运费在内的总价。在这个程序中,为了简单起见,两个产品的价格和数量以及运输价格将直接固定在源代码中(见图 10-2 )。

img/458464_2_En_10_Fig2_HTML.jpg

图 10-2

计算总成本

解决办法

下面是代码:

static void Main(string[] args)
{
    // Fixed values
    const double bookPrice = 29.8;
    const double dvdPrice = 9.9;
    const double shipmentPrice = 25;

    // Inputs
    Console.WriteLine("Order");
    Console.WriteLine("-----");

    Console.Write("Product \"C# Programming for Absolute Beginners (book)\" - enter number of pieces: ");
    string inputBookPieces = Console.ReadLine();
    int bookPieces = Convert.ToInt32(inputBookPieces);

    Console.Write("Product \"All Quiet on Western Front (DVD)\" - enter number of pieces: ");
    string inputDvdPieces = Console.ReadLine();
    int dvdPieces = Convert.ToInt32(inputDvdPieces);

    // Calculations
    double totalForBook = bookPrice * bookPieces;
    double totalForDvd = dvdPrice * dvdPieces;
    double totalForOrder = totalForBook + totalForDvd + shipmentPrice;

    // Outputs
    Console.WriteLine();
    Console.WriteLine("Order calculation");
    Console.WriteLine("-----------------");
    Console.WriteLine("Book: " + totalForBook);
    Console.WriteLine("Dvd: " + totalForDvd);
    Console.WriteLine("Shipment: " + shipmentPrice);
    Console.WriteLine("TOTAL: " + totalForOrder);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

在固定“变量”前加上const表示它们是常量,是在程序运行过程中不会改变的值。Visual Studio 不允许您为这些“变量”赋新值

就我个人而言,我不经常使用const。我只是想给你看一下,以防你在工作过程中看到它。

佣金

在资本主义中,最重要的不是创造、生产或种植什么东西。最重要的是卖!销售人员通常会得到佣金,所以你必须学会如何计算。

工作

您将编写一个程序,接受产品的价格,然后计算商家、分销商和生产商的佣金百分比。从数据中还计算出三方的收益分成(见图 10-3 )。

img/458464_2_En_10_Fig3_HTML.jpg

图 10-3

计算佣金

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter customer price of product: ");
    string inputPrice = Console.ReadLine();
    double customerPrice = Convert.ToDouble(inputPrice);

    Console.Write("Enter merchant commission (percents): ");
    string inputMerchantPercents = Console.ReadLine();
    int merchantPercents = Convert.ToInt32(inputMerchantPercents);

    Console.Write("Enter distributor commission (percents): ");
    string inputDistributorPercents = Console.ReadLine();
    int distributorPercents = Convert.ToInt32(inputDistributorPercents);

    // Calculations
    double coefficient1 = 1 - merchantPercents / 100.0;
    double coefficient2 = 1 - distributorPercents / 100.0;
    double wholesalePrice = customerPrice * coefficient1;
    double priceAfterCommissionSubtraction = wholesalePrice * coefficient2;
    double merchantIncome = customerPrice - wholesalePrice;
    double distributorIncome = wholesalePrice - priceAfterCommissionSubtraction;
    double producerIncome = priceAfterCommissionSubtraction;

    // Outputs
    Console.WriteLine();
    Console.WriteLine("Income division");
    Console.WriteLine("----------------");
    Console.WriteLine("Merchant: " + merchantIncome);
    Console.WriteLine("Distributor: " + distributorIncome);
    Console.WriteLine("Producer: " + producerIncome);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

有时,佣金百分比可能是小数。我在这个例子中选择了整数,因为我想用一个实际的例子向你展示如何正确地划分整数。如您所知,要执行“普通”除法,您至少需要一个数字(在斜杠前面或后面)作为double。这就是你使用100.0的原因。

如果你用100来代替,结果会令人惊讶(见图 10-4 )。

img/458464_2_En_10_Fig4_HTML.jpg

图 10-4

佣金百分比,不正确

你知道为什么会这样吗?都是因为四舍五入。

舍入

金额通常四舍五入到美分。我将向您展示如何做到这一点,以及仅仅为了输出而舍入和为了进一步计算而舍入之间的区别。差别很小,但有时很重要。你可能会漏掉一分钱,给别人带来麻烦。

工作

在用户输入两个货币金额(可能以超过两位小数的方式计算)后,程序将以百分比精度显示它们,将它们四舍五入到美分,最后将原始值与四舍五入值进行比较(图 10-5 )。

img/458464_2_En_10_Fig5_HTML.jpg

图 10-5

舍入程序

解决办法

代码如下:

static void Main(string[] args)
{
    // For simplicity, inputs are fixed in program
    // Some amounts, e.g. after commission calculations, cent fractions are possible
    double amount1 = 1234.567;
    double amount2 = 9.876;

    // Displaying inputs (original values)
    Console.WriteLine("First amount (original value): " + amount1);
    Console.WriteLine("Second amount (original value): " + amount2);
    Console.WriteLine();

    // Rounding just for output
    Console.WriteLine("First amount displayed with cent precision: " + amount1.ToString("N2"));
    Console.WriteLine("Second amount displayed with cent precision: " + amount2.ToString("N2"));
    Console.WriteLine();

    // Rounding for further calculations + informative output
    double roundedAmount1 = Math.Round(amount1, 2); // 2 = two decimal places
    double roundedAmount2 = Math.Round(amount2, 2);

    Console.WriteLine("First amount rounded to cents: " + roundedAmount1);
    Console.WriteLine("Second amount rounded to cents: " + roundedAmount2);
    Console.WriteLine();

    // Calculations
    double sumOfOriginalAmounts = amount1 + amount2;
    double sumOfRoundedAmounts = roundedAmount1 + roundedAmount2;

    // Calculation outputs
    Console.WriteLine("Sum of original amounts: " + sumOfOriginalAmounts.ToString("N2"));
    Console.WriteLine("Sum of rounded amounts: " + sumOfRoundedAmounts.ToString("N2"));
    Console.WriteLine("On invoice, we need sum of rounded amounts");

    // Waiting for Enter
    Console.ReadLine();
}

进一步舍入

有时,舍入会更复杂。

工作

在这项任务中,我将向您展示如何四舍五入到美元,四舍五入到数百美元,始终向下舍入,始终向上舍入(见图 10-6 和 10-7 )。

img/458464_2_En_10_Fig7_HTML.jpg

图 10-7

另一个数字四舍五入到美元,美分,数百美元

img/458464_2_En_10_Fig6_HTML.jpg

图 10-6

更复杂的舍入

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter (decimal) amount in dollars: ");
    string input = Console.ReadLine();
    double amount = Convert.ToDouble(input);

    // To dollars
    double nearest    = Math.Round(amount);
    double alwaysDown = Math.Floor(amount);
    double alwaysUp   = Math.Ceiling(amount);

    Console.WriteLine();
    Console.WriteLine("To dollars");
    Console.WriteLine("----------");
    Console.WriteLine("Nearest    : " + nearest);
    Console.WriteLine("Always down: " + alwaysDown);
    Console.WriteLine("Always up  : " + alwaysUp);

    // To cents
    nearest    = Math.Round(amount, 2);
    alwaysDown = Math.Floor(100 * amount) / 100;
    alwaysUp   = Math.Ceiling(100 * amount) / 100;

    Console.WriteLine();
    Console.WriteLine("To cents");
    Console.WriteLine("--------");
    Console.WriteLine("Nearest    : " + nearest);
    Console.WriteLine("Always down: " + alwaysDown);
    Console.WriteLine("Always up  : " + alwaysUp);

    // To hundreds of dollars
    nearest    = 100 * Math.Round(amount / 100);
    alwaysDown = 100 * Math.Floor(amount / 100);
    alwaysUp   = 100 * Math.Ceiling(amount / 100);

    Console.WriteLine();
    Console.WriteLine("To hundreds of dollars");
    Console.WriteLine("----------------------");
    Console.WriteLine("Nearest    : " + nearest);
    Console.WriteLine("Always down: " + alwaysDown);
    Console.WriteLine("Always up  : " + alwaysUp);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

当然,如果您愿意,也可以使用value.ToString("N2")显示带分的舍入值。

增值税

在欧洲,我们有一个很好的东西叫增值税。每个人都乐于为商品支付更多的钱,如果这能让政客们有更多的预算……实际上,是为了什么?

工作

在该任务中,您将创建一个简单的增值税计算器(参见图 10-8 )。该程序从客户购买产品的价格开始,计算不含增值税(商家从购买中获得)的价格以及增值税本身(商家转移给税务管理员的金额)。

img/458464_2_En_10_Fig8_HTML.jpg

图 10-8

计算增值税

分析

如果你想编写一个程序,你必须首先理解这个程序的本质。那么,欧洲增值税是如何运作的呢?

计算的基础是不含增值税的价格。为了得到这个价格,加上适当的百分比部分(例如,21%),你得到客户支付的价格。重要的是,百分比是根据不含增值税的价格计算的,而不是根据客户价格计算的(见图 10-9 )!

img/458464_2_En_10_Fig9_HTML.jpg

图 10-9

了解增值税的工作原理

例如,如果增值税率为 21%,您需要将客户价格除以 1.21,以获得不含增值税的价格。对于税率的一般值,您可以通过将适当的分数加到 1 来计算除数。

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter customer price of a product: ");
    string inputPrice = Console.ReadLine();
    double customerPrice = Convert.ToDouble(inputPrice);

    Console.Write("Enter VAT rate in %: ");
    string inputVatRate = Console.ReadLine();
    double vatRate = Convert.ToDouble(inputVatRate);

    // Calculations
    double divisor = 1 + vatRate / 100.0;
    double calculatedPriceWithoutVat = customerPrice / divisor;
    double priceWithoutVat = Math.Round(calculatedPriceWithoutVat, 2);
    double vat = customerPrice - priceWithoutVat;

    // Outputs
    Console.WriteLine();
    Console.WriteLine("Price without VAT: " + priceWithoutVat.ToString("N2"));
    Console.WriteLine("VAT: " + vat.ToString("N2"));

    // Waiting for Enter
    Console.ReadLine();
}

摘要

在这一章中,你练习了经济世界中各种真实例子的计算。在这样的计算中,最重要的是首先理解现实世界的问题。为了理解在没有程序的情况下如何得到结果,从一支铅笔、一张纸和一个计算器开始。适当地组织你的程序,将整个计算分成小块,并为你的变量使用描述性的名字,通常也是有帮助的。

除此之外,你还学习了如何舍入。具体来说,您学习了几个内置的数学函数:

  • 您知道如何使用Math.Round进行最常见的舍入,换句话说,舍入到最接近的整数。您可以在方法调用的第二个参数中指定所需的小数位数。

  • 您知道如何使用Math.Floor始终向下舍入,换句话说,舍入到小于或等于被舍入数字的最大整数。

  • 您知道如何使用Math.Ceiling始终向上舍入,换句话说,舍入到大于或等于被舍入数字的最小整数。

您还学习了如何舍入到百位数的技巧,包括在舍入前除以 100,然后乘以相同的数。

十一、使用日期的计算

在上一章中,您练习了经济领域的计算。你也经常需要计算日期。假设您需要设置发票的到期日期。或者假设您想要计算发票过期多少天。或者,您可能需要知道某个特定时期(如一个月或一个季度)的第一天和最后一天。在这一章中,你将学习如何计算日期。

日期输入

首先,您将学习如何从用户那里读取日期。我也将向您展示一些简单的日期算法。

工作

在这个任务中,您将获得一个基于用户输入的DateTime对象。之后,您将计算下一天和前一天(见图 11-1 )。

img/458464_2_En_11_Fig1_HTML.jpg

图 11-1

计算下一天和前一天

解决办法

重点是使用Convert.ToDateTime方法。如果用户输入一个不存在的日子(例如,非闰年的 2 月 29 日),该方法会导致一个运行时错误,您可以使用try-catch构造来处理这个错误。

static void Main(string[] args)
{
    try
    {
        // Text input of date
        Console.Write("Enter date: ");
        string input = Console.ReadLine();

        // Conversion to DateTime object
        DateTime enteredDate = Convert.ToDateTime(input);

        // Some calculations
        DateTime followingDay = enteredDate.AddDays(1);
        DateTime previousDay  = enteredDate.AddDays(-1);

        // Outputs
        Console.WriteLine();
        Console.WriteLine("Entered day  : " + enteredDate.ToLongDateString());
        Console.WriteLine("Following day: " + followingDay.ToLongDateString());
        Console.WriteLine("Previous day : " + previousDay.ToLongDateString());
    }
    catch (Exception)
    {
        // Treating incorrect input
        Console.WriteLine("Incorrect input");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

也可以用两个参数而不是一个来调用Convert.ToDateTime方法。第二个参数是语言设置,也就是你已经知道的CultureInfo对象。这与其他转换方法类似。

单月

现在,您将练习使用DateTime组件,并使用构造函数调用创建该对象。

工作

用户输入日期。该程序显示输入日期所在月份的第一天和最后一天(见图 11-2 )。

img/458464_2_En_11_Fig2_HTML.jpg

图 11-2

计算每月的第一天和最后一天

解决办法

代码如下:

static void Main(string[] args)
{
    // Date input
    Console.Write("Enter a date: ");
    string input = Console.ReadLine();
    DateTime enteredDate = Convert.ToDateTime(input);

    // Calculations
    int enteredYear = enteredDate.Year;
    int enteredMonth = enteredDate.Month;

    DateTime firstDayOfMonth = new DateTime(enteredYear, enteredMonth, 1);
    DateTime lastDayOfMonth = firstDayOfMonth.AddMonths(1).AddDays(-1);

    // Outputs
    Console.WriteLine();
    Console.WriteLine("Corresponding month: " +
        "from " + firstDayOfMonth.ToShortDateString() +
        " to " + lastDayOfMonth.ToShortDateString());

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 根据前面的练习,您使用Convert.ToDateTime方法调用从用户那里获得一个DateTime对象。

  • 您开始从输入的日期中选择月份和年份。为此,您可以使用MonthYear属性。

  • 使用这些数字,您可以很容易地组合出一个月的第一天,因为它的天数始终是一。

  • 一个月的最后一天并不容易,因为月份的长度不同。诀窍就是加一个月,减一天!

  • 注意,我没有在任何地方存储AddMonth的结果。我直接调用AddDays代替它。这被称为方法链接

  • 为了简单起见,我在这里不处理不正确输入的可能性。

四分之一

继续讨论日期,我将向您展示一些有趣的技巧,您有时必须使用这些技巧来获得正确的结果。

工作

对于输入的日期,该程序将显示该日期所属季度的开始、结束和编号(从一到四)(见图 11-3 和 11-4 )。

img/458464_2_En_11_Fig4_HTML.jpg

图 11-4

显示相应的季度,另一个例子

img/458464_2_En_11_Fig3_HTML.jpg

图 11-3

显示相应的季度

分析

这项任务的关键是确定季度的数字。接下来是该季度的第一个月。

季度编号

您需要将月份数转换为季度数,如下所示:

  • 第 1、2 或 3 个月= 1

  • 第 4、5 或 6 个月= 2

  • 第 7、8 或 9 个月= 3

  • 第 10、11 或 12 个月= 4

这是使用整数除法的一个很好的例子。您可以看到,首先需要在月份数字上加 2,然后执行整数除以 3:

int numberOfQuarter = (enteredMonth + 2) / 3;

季度的第一个月数字

如果您已经有了该季度的编号,您将得到该季度的第一个月,如下所示:

  • 第一季度为 1 月

  • 第二季度为 4 月

  • 第三季度 7 月

  • 第四季度 10 月

你可能意识到季度的数字必须乘以三。为了得到正确的结果,你需要连续减去两个:

int monthOfQuarterStart = 3 * numberOfQuarter - 2;

第一天和最后一天

有了第一个月的时间,你可以按照与上一个练习相似的步骤进行。要获得第一天,您可以使用将天数设置为 1 的DateTime构造函数。要得到最后一天,你要加上三个月,减去一天。

解决办法

代码如下:

static void Main(string[] args)
{
    // Date input
    Console.Write("Enter a date: ");
    string input = Console.ReadLine();
    DateTime enteredDate = Convert.ToDateTime(input);

    // Calculations
    int enteredYear = enteredDate.Year;
    int enteredMonth = enteredDate.Month;

    int numberOfQuarter = (enteredMonth + 2) / 3;
    int monthOfQuarterStart = 3 * numberOfQuarter - 2;
    DateTime firstDayOfQuarter = new DateTime(enteredYear, monthOfQuarterStart, 1);
    DateTime lastDayOfQuarter = firstDayOfQuarter.AddMonths(3).AddDays(-1);

    // Outputs
    Console.WriteLine();
    Console.WriteLine("Corresponding quarter: " +
        "number-" + numberOfQuarter +
        ", from " + firstDayOfQuarter.ToShortDateString() +
        " to " + lastDayOfQuarter.ToShortDateString());

    // Waiting for Enter
    Console.ReadLine();
}

日期差异

您经常需要计算两个特定日期之间的时间跨度,换句话说,在输入的日期之间经过了多少天或多少年。这就是你现在要学习的内容。

工作

用户输入他们的出生日期。该程序显示世界有多少天乐于拥有它们(见图 11-5 )。

img/458464_2_En_11_Fig5_HTML.jpg

图 11-5

计算还能活多少天

解决办法

如您所见,您需要从今天的日期中减去出生日期。当您减去日期时,结果是一个TimeSpan对象。有了这个对象,您可以使用它的许多属性中的一个。在本练习中,您将使用Days属性。

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter your date of birth: ");
    string input = Console.ReadLine();
    DateTime dateOfBirth = Convert.ToDateTime(input);

    // Today
    DateTime today = DateTime.Today;

    // Date difference
    TimeSpan difference = today - dateOfBirth;
    int numberOfDays = difference.Days;

    // Output
    Console.WriteLine();
    Console.WriteLine("Today is: " + today.ToShortDateString());
    Console.WriteLine("The world likes you for this number of days: " + numberOfDays.ToString("N0"));

    // Waiting for Enter
    Console.ReadLine();
}

时区和 UTC

如果您想要存储某件事情发生的时刻(例如,记录订单、问题等),您可能会对夏令时感到意外。或者,也许更重要的是,假设您正在创建一个将在全球范围内运行的程序。你必须适应不同的时区。

为了处理这些情况,了解如何使用协调世界时(UTC)是很好的,协调世界时是指不含食品添加剂的零子午线时间。对不起,我的意思是不受夏令时限制。UTC 是独立于时区的。

熟悉除了日期和时间之外还包含时区信息的DateTimeOffset对象也很有好处。

工作

在本练习中,我将向您展示如何使用 UTC 和包含在DateTimeOffset对象中的时区。您将创建一个处理当前时间的程序(参见图 11-6 )。

img/458464_2_En_11_Fig6_HTML.jpg

图 11-6

日期时间关闭对象

解决办法

代码如下:

static void Main(string[] args)
{
    // Current time serves as input
    DateTime now = DateTime.Now;
    DateTime utcNow = DateTime.UtcNow;
    DateTimeOffset completeInstant = DateTimeOffset.Now;
    DateTimeOffset utcCompleteInstant = DateTimeOffset.UtcNow;

    // Outputs
    Console.WriteLine("Now: " + now);
    Console.WriteLine("UTC now: " + utcNow);
    Console.WriteLine("Now (including time zone): " + completeInstant);
    Console.WriteLine("Time zone (offset against UTC): " + completeInstant.Offset.TotalHours);
    Console.WriteLine("UTC now (including time zone): " + utcCompleteInstant);

    // Waiting for Enter
    Console.ReadLine();
}

请注意,一些变量属于DateTime类型,而其他变量属于DateTimeOffset类型。

摘要

在这一章里,你相当彻底地学会了如何用日期做计算。

首先使用Convert.ToDateTime方法从用户处获取日期,然后使用DateTime的构造函数调用(new DateTime …)从指定的年、月、日获取日期。

在您的计算中,您适当地使用了DateTime对象的各种属性,如DayMonthYear,以及它的方法,如AddDays。此外,为了计算一个季度的数字,你使用了整数除法。

此外,您还熟悉了如何计算任意两个给定日期之间的差异,以及如何处理结果。具体来说,您使用了TimeSpan对象。

最后,我们讨论了 UTC 和时区,以便于程序跨多个时区运行,并正确处理夏令时带来的时间跳跃。具体来说,您了解了DateTimeOffset对象。

十二、理解不同种类的数字

在这一章中,你将学习一些关于数字和计算的更高级的主题,比如更多的数字类型、内存消耗和溢出。如果此时你不需要这么多的细节,你可以安全地跳过这一章或者只是浏览一下。

更多数字类型

你已经知道在计算中整数和小数是有区别的。使用int类型表示整数,使用double类型表示小数。

但是 C# 中还有其他数字数据类型。虽然它们中的许多主要是因为历史原因而存在,并且你可能永远不会使用它们,但至少了解它们是有好处的。

工作

您将编写一个程序,显示所有 C# 数值数据类型的概述。对于每种类型,其可能值的范围将被打印出来(见图 12-1 )。

img/458464_2_En_12_Fig1_HTML.jpg

图 12-1

打印所有数字数据类型

解决办法

代码如下:

static void Main(string[] args)
{
    // Immediately outputs
    Console.WriteLine("Signed whole numbers");
    Console.WriteLine("--------------------");
    Console.WriteLine("sbyte:  " + sbyte.MinValue + " to " + sbyte.MaxValue);
    Console.WriteLine("short:  " + short.MinValue + " to " + short.MaxValue);

    Console.WriteLine("int:    " + int.MinValue + " to " + int.MaxValue);
    Console.WriteLine("long:   " + long.MinValue + " to " + long.MaxValue);
    Console.WriteLine();

    Console.WriteLine("Unsigned whole numbers");
    Console.WriteLine("----------------------");
    Console.WriteLine("byte:   " + byte.MinValue + " to " + byte.MaxValue);
    Console.WriteLine("ushort: " + ushort.MinValue + " to " + ushort.MaxValue);
    Console.WriteLine("unit: " + uint.MinValue + " to " + uint.MaxValue);
    Console.WriteLine("ulong: " + ulong.MinValue + " to " + ulong.MaxValue);
    Console.WriteLine();

    Console.WriteLine("Basic decimal numbers");
    Console.WriteLine("---------------------");
    Console.WriteLine("float:  " + float.MinValue + " to " + float.MaxValue);
    Console.WriteLine("double: " + double.MinValue + " to " + double.MaxValue);
    Console.WriteLine();

    Console.WriteLine("Exact decimal numbers");
    Console.WriteLine("---------------------");
    Console.WriteLine("decimal:  " + decimal.MinValue + " to " + decimal.MaxValue);

    // Waiting for Enter
    Console.ReadLine();
}

注意

为了显示范围,我使用了所有数字数据类型的MinValueMaxValue属性。

讨论

下面几节讨论这个程序。

无符号数字

程序打印出来的结果显示,有些数据类型不允许存储负数!然而,除了从文件、数据库或 web 服务中读取二进制数据时使用的byte类型,这些无符号数字很少被使用。

与有符号的数字相反,无符号的数字通常以一个 u 开头,意思是“无符号的”类似地,有符号类型sbytes 开始,意味着更重要的byte的“有符号”变体。

十进制数字

十进制类型范围以科学记数法显示(也称为指数记数法)。比如最大的float数显示为 3.4E+38,表示 3.4 乘以 10 的 38 次方。这是一个很大的数字,不是吗?

十进制类型的精度也不同。float型存储大约 7 位有效数字的十进制值,而double型提供大约 15 位有效数字的精度,而decimal型提供 28 位有效数字。

特殊类型十进制

decimal数据类型有些特殊。由于以下原因,最好在处理货币时使用它:

  • 它精确地存储分数值。例如,12.80 的金额将被精确地存储为 12.80,而不是像 12.7999999999 这样的数字,使用其他类型可能会出现这种情况。

  • 因为有大量的有效数字,decimal数据类型允许您表示大量的钱,并且仍然保持分的精度。

然而,这两个理由并不像看起来那样令人信服。如果您正确地执行了舍入,您可以用double类型准确地存储美分。而且坦率的说,除了double 15 位数够不够钱的问题,你通常还需要解决其他问题!

此外,许多事情用double类型更容易,这就是为什么我在本书中更喜欢用double表示小数。

最后一点:使用decimal类型的计算比使用double类型的计算慢得多(事实上,慢了几百倍)。如果您只处理几个数字,这并不重要,但是在大型数据集中,这种差异可能非常显著。

内存消耗

如果您对位和字节有所了解,您可能会想到,由于相应类型可用的内存空间不同,类型范围也会有所不同。这是完全正确的,您将在本节中了解更多信息。

工作

在本节中,您将编写一个程序,告诉您每种类型使用多少字节的内存(见图 12-2 )。

img/458464_2_En_12_Fig2_HTML.jpg

图 12-2

显示每种类型使用的字节数

解决办法

代码如下:

static void Main(string[] args)
{
    // Outputs
    Console.WriteLine("Whole numbers");
    Console.WriteLine("-------------");
    Console.WriteLine("byte:   " + sizeof(byte));
    Console.WriteLine("sbyte:  " + sizeof(sbyte));
    Console.WriteLine();
    Console.WriteLine("short:  " + sizeof(short));
    Console.WriteLine("ushort: " + sizeof(ushort));
    Console.WriteLine();
    Console.WriteLine("int:    " + sizeof(int));
    Console.WriteLine("uint:   " + sizeof(uint));
    Console.WriteLine();
    Console.WriteLine("long:   " + sizeof(long));
    Console.WriteLine("ulong:  " + sizeof(ulong));
    Console.WriteLine();
    Console.WriteLine("Decimal numbers");
    Console.WriteLine("---------------");
    Console.WriteLine("float:    " + sizeof(float));
    Console.WriteLine("double:   " + sizeof(double));
    Console.WriteLine("decimal:  " + sizeof(decimal));
    Console.WriteLine();

    // Waiting for Enter
    Console.ReadLine();
}

连接

可以连接当前和以前程序的结果。比如我们来讨论一下重要的int型。它使用 4 字节或 32 位内存。这意味着可能值的 2 的 32 次方,超过 40 亿。int是有符号类型,所以正数有二十亿,负数有二十亿。它的无符号对应物uint有所有 40 亿个正数的值(当然,还有零)。

讨论

您可能会对数字数据类型的多样性感到困惑。为了帮助您理解它们,以下是您应该何时使用每种方法的总结:

  • int:对于固有整数值的常规工作(例如,某物的计数)。

  • double:用于处理可能是小数的值(如测量值)或数学运算值的常规工作。钱数也大多可以。

  • byte:用于处理二进制数据。

  • long:用于大整数值,如文件大小、支付标识(如可能需要十位数字)或常规(整)值的乘法结果。

  • 金额的常见选择。

其他类型不经常使用。

泛滥

当程序计算出一个不“适合”适当类型范围的值时,发生的情况称为溢出。你的程序的行为可能非常奇怪,如图 12-3 所示。

img/458464_2_En_12_Fig3_HTML.jpg

图 12-3

泛滥

因为乘法通常会产生很大的数字,所以溢出尤其会在乘法时发生。

工作

在本节中,您将编写一个程序,尝试计算一百万乘以一百万。

解决办法

代码如下:

static void Main(string[] args)
{
    // Multiplying million by million
    int million = 1000000;
    int result = million * million;
    long resultInLong = million * million;

    // Outputs
    Console.WriteLine("Million times million: " + result);
    Console.WriteLine("also in long: " + resultInLong);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

程序做的事情完全出乎意料。你需要意识到这种异常。

实际发生了什么?这个程序将一百万乘以一百万。结果太大,不适合 32 位有符号int类型的正负 20 亿范围。所以,计算机简单地丢弃了高位,导致完全无意义。

请注意,即使将结果存储在一个long类型的变量中,也会得到同样的无意义结果。在计算过程中,会出现丢弃大于 32 位的无意义数据。根据 C# 规则,int乘以int就是int,不管你把结果存储在哪里。

处理溢出

前一个程序显示了不正确的结果。现在你将看到对此能做些什么。

工作

以下是如何处理溢出问题的两种可能性:

  • 如果你不期望一个很大的值,但它还是出现了,程序至少应该崩溃或者让你知道这个问题。显示一个无意义的值是最坏的选择。用户信任他们的计算机,并且会因为相信不正确的结果而做出错误的决定。

  • 如果您认为int可能不够,您可以使用以下解决方案进行正确计算。

解决办法

新项目源代码如下:

static void Main(string[] args)
{
    // 0\. Preparation
    int million = 1000000;

    // 1\. Crash at least, we do not
    //    definitely want a nonsense
    Console.WriteLine("1\. calculation");
    try
    {
        long result = million * million;
        Console.WriteLine("Million times million:" + result);
    }
    catch (Exception)
    {
        Console.WriteLine("I cannot calculate this.");
    }

    // 2\. Correct calculation of a big value
    Console.WriteLine("2\. calculation");
    long millionInLong = million;
    long correctResult = millionInLong * millionInLong;
    Console.WriteLine("Million times million: " + correctResult.ToString("N0"));

    // 3\. Alternative calculation of a big valule
    Console.WriteLine("3\. calculation");
    long correctResultAlternatively = (long)million * (long)million;
    Console.WriteLine("Million times million: " + correctResultAlternatively.ToString("N0"));

    // Waiting for Enter
    Console.ReadLine();
}

注意

然而,这个代码并不能解决所有问题。当你立即启动程序时,第一次计算仍然是错误的。人们有时会把try-catch当成一种灵丹妙药,但绝对不是。你需要别的东西,正如接下来要讨论的。

Visual Studio 中的设置

您需要在 Visual Studio 中设置您的项目,以便它报告程序溢出,而不是掩盖它。

从 Visual Studio 菜单中,选择项目,然后选择属性(参见图 12-4 )。

img/458464_2_En_12_Fig4_HTML.jpg

图 12-4

开启属性

接下来选择 Build 选项卡,垂直(也可能水平)滚动,这样你就可以看到高级按钮(它真的被隐藏了!),然后点击该按钮(参见图 12-5 )。

img/458464_2_En_12_Fig5_HTML.jpg

图 12-5

构建选项卡

在出现的对话框中,选择“检查算术溢出/下溢”复选框,并点击确定按钮确认(见图 12-6 )。

img/458464_2_En_12_Fig6_HTML.jpg

图 12-6

“检查算术溢出/下溢”复选框

您的项目现在终于可以运行了。

结果

现在程序按照预期运行,如图 12-7 所示。

img/458464_2_En_12_Fig7_HTML.jpg

图 12-7

用一百万乘以一百万

第一种选择

第一个计算正确地报告了一个问题。省略try-catch构造会导致运行时错误,但至少它不会显示不正确的结果。

其他选择

在计算开始之前,正确的计算将百万转换成long类型*。以下是执行这种转换的两种方法:*

  • 将百万分配给类型为long的变量

  • 使用带有(long)million的显式类型转换

如果不需要精确的整数运算,也可以使用double类型进行计算。除非解决一些很奇特的数学,double没有溢出的机会。

摘要

在本章中,您学习了高级数字计算。您必须了解 C# 中所有可用的数字数据类型。类型的不同之处在于它们允许整数还是小数,并且它们允许的值的范围也不同。小数的类型在存储数字的精度上也互不相同。

初级水平,有intdouble的知识就够了;你可以一直只使用它们。当你变得更有经验时,你也可以使用下面的方法:

  • 用于大整数的long类型,如文件大小、十位数的支付数字或中等大小数字的乘法结果

  • 用于处理货币的decimal类型

  • 用于处理二进制数据的byte类型

你也研究了溢出的问题。当计算出的值太大而不适合特定数据类型的范围时,就会产生无意义的结果。Visual Studio 的默认行为是照常继续。然而,现在您知道如何更改设置以至少导致运行时错误,因为继续得到不正确的结果是最坏的选择。

最好的替代方法是通过选择具有适当范围的数据类型来完全避免溢出。但是,请记住,改变用于存储结果的变量类型可能还不够。例如,int乘以int始终是int,最大值约为 20 亿,不管你把它存储在哪里。在计算之前将数字转换成long类型可能是合适的。

十三、累积值

到目前为止,您已经处理了存储了一个以后要使用的值的变量。初始赋值后,变量的值没有改变。现在,您已经准备好进入下一步,即研究在程序运行期间变量值发生变化的情况,换句话说,就是从旧值中确定新值。

十更,重访

首先,你将回到在第八章中学习的给一个数加 10 的任务。该程序的目标是呈现一个比用户输入的数字大 10 的值(见图 13-1 )。

img/458464_2_En_13_Fig1_HTML.jpg

图 13-1

显示用户号码加 10

工作

你现在将以一种新的方式解决这项任务;具体来说,您将把计算结果存储在最初存储输入数字的同一个变量中。

这不一定是更好的解决方案,但是您将在后面的章节中学习如何进一步构建它。

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Calculation
    number = number + 10;

    // Result output
    Console.WriteLine("Number ten more greater is: " + number);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

解决方案的核心语句如下:number = number + 10;。这个声明是不寻常的,因为同一个东西——变量number—出现在等号的两边!

计算机执行语句是这样的:“取变量number的当前值,加十,将结果存储为变量number的新值。”因此,该语句的净结果是将number的价值增加了 10。

复合赋值

做同样的事情有一个很好的捷径,叫做复合赋值。你现在要研究这个。

工作

您将使用更简洁的复合赋值来解决上一个练习。

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Calculation using compound assignment
    number += 10; // same as number = number + 10;

    // Result output
    Console.WriteLine("Number ten more greater is: " + number);

    // Waiting for Enter
    Console.ReadLine();
}

注意

在这段代码中,您使用了复合赋值操作符(+=),这是一个快捷方式,其功能与前面的解决方案相同。你会在所有 C 系列编程语言中看到复合赋值。

进一步的复合赋值

你喜欢复合作业吗?在处理其他算术运算时,还可以使用更多类似的赋值。

工作

我将向你展示一个结合减法、乘法和除法来说明复合赋值的程序(见图 13-2 )。

img/458464_2_En_13_Fig2_HTML.jpg

图 13-2

复合赋值

解决办法

代码如下:

static void Main(string[] args)

{
    // Input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);
    Console.WriteLine();

    // With subtraction
    number -= 5; // same as number = number - 5;
    Console.WriteLine("After decrease by 5: " + number);

    // With multiplication
    number *= 10; // same as number = number * 10;
    Console.WriteLine("Ten times greater: " + number);

    // With division
    number /= 2; // same as number = number / 2;
    Console.WriteLine("Decreased to one half: " + number);

    // Waiting for Enter
    Console.ReadLine();
}

注意

程序每次都使用相同的变量!

这里的除法是整数除法,因为number和 2 都是int

递增和递减

到目前为止,变量最频繁的变化是一个一个的变化。这就是为什么有特殊的超级简洁的方法来进行这样的计算。

工作

现在你将熟悉递增运算符(++)和递减运算符(--),如图 13-3 所示。

img/458464_2_En_13_Fig3_HTML.jpg

图 13-3

递增和递减运算符

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Increasing by 1 using INCREMENT OPERATOR
    number++; // same as number = number + 1;
    Console.WriteLine("Increased by 1: " + number);

    // Decreasing by 1 using DECREMENT OPERATOR
    number--; // same as number = number - 1;
    Console.WriteLine("Back again: " + number);

    // Waiting for Enter
    Console.ReadLine();
}

复合赋值和文本

由于+操作符可以用于文本,所以您也可以将复合赋值操作符(+=)用于文本。你可能会经常用到它。

工作

该任务将使你熟悉使用复合赋值的文本连接(见图 13-4 )。

img/458464_2_En_13_Fig4_HTML.jpg

图 13-4

使用复合赋值的文本连接

解决办法

代码如下:

static void Main(string[] args)
{
    // Initial value (empty text)
    string books = "";

    // Appending
    books += "Homage to Catalonia" + Environment.NewLine;
    books += "Silent Spring" + Environment.NewLine;
    books += "The beat of a different drum" + Environment.NewLine;

    // Output
    Console.WriteLine("Valuable books");
    Console.WriteLine("--------------");
    Console.WriteLine(books);

    // Waiting for Enter
    Console.ReadLine();
}

渐进求和

渐进求和是对大量值求和的一个重要原则。这意味着不是在一个语句中一次性将它们相加,而是逐个相加,在一个特殊的变量中逐步累积中间结果。

工作

你将编写一个程序,逐步将三个输入的数字相加。当然,在一行中一次性将三个数相加会更方便。然而,我想用一个简单的例子来说明渐进求和的重要原理,并让你在讨论更复杂的话题,即循环之前习惯这个想法(见图 13-5 )。

img/458464_2_En_13_Fig5_HTML.jpg

图 13-5

逐步累加三个输入的数字

解决办法

代码如下:

static void Main(string[] args)
{
    // Preparation - variable to accumulate intemediate result
    int sum = 0;

    // Input - 1\. number
    Console.Write("Enter first number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Adding first number to intermediate result
    sum += number;

    // Input - 2\. number
    Console.Write("Enter second number: ");
    input = Console.ReadLine();
    number = Convert.ToInt32(input);

    // Adding second number to intermediate result
    sum += number;

    // Input - 3\. number
    Console.Write("Enter third number: ");
    input = Console.ReadLine();
    number = Convert.ToInt32(input);

    // Adding third number to intermediate result
    sum += number;

    // Output
    Console.WriteLine();
    Console.WriteLine("Sum of entered numbers: " + sum);

    // Waiting for Enter
    Console.ReadLine();
}

多文本连接

同样,因为+操作符也可以用于文本,所以您可以将渐进求和的原则扩展到文本。在这种情况下,不妨称之为渐进积累

工作

你将编写一个程序,逐步积累用户输入的名字。做两次积累会很有意思;第一个是原顺序,第二个是倒顺序。

为简单起见,您将只使用三个值(见图 13-6 )。

img/458464_2_En_13_Fig6_HTML.jpg

图 13-6

逐渐积累名字

解决办法

代码如下:

static void Main(string[] args)
{
    // Preparation - variables to accumulate intermediate results
    string inOriginalOrder = "";
    string inReversedOrder = "";

    // Input of the first person
    Console.Write("Enter first person: ");
    string person = Console.ReadLine();

    // Appending the first person to intermediate result
    inOriginalOrder += person + Environment.NewLine;
    inReversedOrder = person + Environment.NewLine + inReversedOrder;

    // Input of the second person
    Console.Write("Enter second person: ");
    person = Console.ReadLine();

    // Appending the second person to intermediate result
    inOriginalOrder += person + Environment.NewLine;
    inReversedOrder = person + Environment.NewLine + inReversedOrder;

    // Input of the third person
    Console.Write("Enter third person: ");
    person = Console.ReadLine();

    // Appending the third person to intermediate result
    inOriginalOrder += person + Environment.NewLine;
    inReversedOrder = person + Environment.NewLine + inReversedOrder;

    // Output
    Console.WriteLine();
    Console.WriteLine("Entered persons");
    Console.WriteLine("---------------");
    Console.WriteLine(inOriginalOrder);
    Console.WriteLine("In reversed order");
    Console.WriteLine("-----------------");
    Console.WriteLine(inReversedOrder);

    // Waiting for Enter
    Console.ReadLine();
}

注意

有趣的是,当以相反的顺序连接人名时,复合赋值没有帮助。

摘要

本章的中心主题是同一变量中值的累积。与迄今为止的程序相反,这里的程序重复地改变变量的值,通常使用它的初始值,并以某种方式修改它。具体来说,您学习了以下内容:

  • variable = variable + change;这样的语句获取variable的当前值,加上change,并将结果存储为variable的新值

  • 复合赋值,如variable += change;,它是前面语句的简短等效

  • 与其他算术运算的复合赋值:-=*=/=

  • 带文本的复合赋值(仅限+=)

  • 使用variable++;variable--;的超短符号增加(加 1)和减少(减 1)变量

在本章的最后,你已经熟悉了渐进求和(和渐进累加)的原理,这意味着一个接一个地对数字求和,同时将中间结果存储在一个特殊的变量中。这个原则主要用在对大量值求和时,在本书后面研究循环时,你会体会到它的极端重要性。