【收藏夹吃灰系列】C# 基础入门练习 | 最佳实践

1,446 阅读6分钟

一、前言

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情C# 基础入门练习案例看这个就够了,又是收藏夹吃灰系列【doge

二、练习框架

2.1 构建

在应用程序入口点构建一个重复运行测试的代码框架,同时也是在应用程序启动时由运行时自动调用的方法。

/***************/
/*定义运行模块区*/
/***************/
namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            do
            {
                Console.WriteLine($"{DateTime.Now}: 我是人工智能小障,请问需要什么服务?");
                /**********************************************************************/
                // 练习模块
                Hello();
                /**********************************************************************/
                Console.Write($"{DateTime.Now}: Exit or not ?(y/n): ");
                ConsoleKey pressKey = Console.ReadKey().Key;
                if (pressKey == ConsoleKey.Y)
                {
                    break;
                }
                else if (pressKey != ConsoleKey.N)
                {
                    Console.Write($"\n{DateTime.Now}: Warning: no exit by default...");
                }
                Console.WriteLine("\n-------------------------------------------------------");
            } while (true);
            // Environment.NewLine <==> \n
            Console.Write($"\n{Environment.NewLine}按任意键退出……");
            Console.ReadKey(true); // 运行完结果不退出控制台
        }

        // 第一个练习模块:Hello World.
        static void Hello()
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

Main() 入口主函数在类或结构中声明。

  • Main 必须是 static,它不需要是 public
  • 它获得的是 private 成员的默认访问权限

args 数组中包含在应用程序启动时提供的所有命令行自变量。

  • args 数组不能为 null
  • 无需进行 null 检查即可放心地访问 Length 属性

2.2 效果

开始执行不调试(Ctrl + F5),运行效果如图所示:

image.png

从键盘上读取输入,是否继续执行练习模块代码,以便后续观察多种运行结果。学习任何一门语言都离不开输出 Hello World 字符串,这是学习 C# 的第一个练习测试程序,赶快来试试吧!我们马上进入到基础部分的学习。

2.3 预处理器指令

使用预处理指令来控制运行哪一个练习模块的代码。使用 #if 指令来创建一个条件指令。条件指令用于测试符号是否为真。如果为真,编译器会执行 #if 和下一个指令之间的代码;使用 #region 指令可以指定一个可展开或折叠的代码块。

练习模块区:

... ...
... ...
                #region 1、字符串练习
#if ONE
                demo.stringDemo.AskGreeting.greet();
#endif
                #endregion
                ... ...
                ... ...

使用 #define 宏定义执行哪一个练习模块:

#define ONE
... ...
... ...

如果不再执行该练习模块,则将 #define ... 一行删除或将 #define ... 改成 #undef ...

例如:

#undef ONE
... ...
... ...

三、基础练习

3.1 字符串

第一个入门代码是在命令行窗口输出 Hello World 字符串,字符串的操作在以后的开发和学习中都经常使用,所以第一个基础练习就是字符串的相关操作了。

C# 中,您可以使用字符数组来表示字符串,但是,更常见的做法是使用 string 关键字来声明一个字符串变量。string 关键字是 System.String 类的别名。

如何创建 string 对象:

  1. 通过给 String 变量指定一个字符串
  2. 通过使用 String 类构造函数
  3. 通过使用字符串串联运算符( +
  4. 通过检索属性或调用一个返回字符串的方法
  5. 通过格式化方法来转换一个值或对象为它的字符串表示形式
namespace demo.stringDemo {
    /// <summary>
    /// 字符串操作学习
    /// </summary>
    class AskGreeting {
        /// <summary>
        /// 根据输入的姓名以姓氏打招呼
        /// </summary>
        public static void greet() {
            string? name;
            while (true) {
                Console.Write($"{DateTime.Now}: 请输入你的名字:");
                name = Console.ReadLine();
                if (!string.IsNullOrWhiteSpace(name)) {
                    break;
                }
                Console.WriteLine($"{DateTime.Now}: 输入无效,请重新输入!");
            }
            // WriteLine() 方法输出完后自动会换行。
            Console.WriteLine($"{DateTime.Now}: 你好,{name.Substring(0, 1)}先生!");
        }
    }
}

注意: 添加关键字 public ,引用该命名空间时可以调用 AskGreeting 类中的静态方法。

3.1.1 运行

image.png

根据输入的姓名,以姓氏打招呼。输入的姓名字符串使用可空的 name 变量进行接收,然后使用 string.IsNullOrWhiteSpace() 方法判断传入的字符串参数是否为 null 或空格组成,如果满足条件则返回真,否则返回假。

如果用户不输入那我们直接循环让用户重复输入合法姓名。

拿到合法姓名后,我们使用 name.Substring(0, 1) 取其中第一个字符串子串作为姓氏尊称,使用字符串模板书写出来!

3.2 字符ASCII

说完字符串操作之后,紧接着就是字符相关操作的学习了。学习字符就离不开字符和 ASCII(美国信息交换标准代码)相互转换了,这里面涉及到用户字符单个输入和类型转换问题,对输入判断会比较复杂些。

字符转 ASCII 码值很简单,只需要将用户输入的一个字符使用 (int) 强转过去就好了。但是 ASCII 码值转字符的话,就涉及到输入的值是控制字符无法很好地呈现出来。

输入校验逻辑:

namespace demo.charDemo
{
    /// <summary>
    /// 字符ASCII码学习
    /// </summary>
    class CharASCII {
        /// <summary>
        /// 字符与ASCII码互转,交互告知
        /// </summary>
        public static void tell() {
            string? choice;
            while (true) {
                Console.WriteLine("1. 字符转ASCII码");
                Console.WriteLine("2. ASCII码转字符");
                Console.Write($"{DateTime.Now}: 您选择:");
                choice = Console.ReadLine();
                if (isInRangedNumber(choice, '1', '2')) {
                    break;
                }
                Console.WriteLine($"{DateTime.Now}: 输入无效,请重新输入!");
            }
            ... ...
            ... ...
        }
    }
}

方法:判断单个字符是否在指定的数字范围内:

        /// <summary>
        ///  判断单个字符是否在指定的数字范围内
        /// </summary>
        /// <param name="str">字符串</param>
        /// <param name="startNum">范围开始</param>
        /// <param name="endNum">范围结束</param>
        /// <returns>真假值</returns>
        static bool isInRangedNumber(string? str, char startNum, char endNum) {
            char res = strToSingleChar(str);
            return res >= startNum && res <= endNum;
        }

方法:将字符串处理成一个字符:

        /// <summary>
        /// 将字符串处理成一个字符
        /// </summary>
        /// <param name="str">字符串</param>
        /// <returns>第一个字符或空字符</returns>
        static char strToSingleChar(string? str) {
            if (!string.IsNullOrWhiteSpace(str) && str.Trim().Length == 1) {
                return getFirstChar(str);
            }
            return ' ';
        }

方法:返回字符串中的第一个字符:

        /// <summary>
        /// 返回字符串中的第一个字符
        /// </summary>
        /// <param name="str">字符串</param>
        /// <returns>第一个字符</returns>
        static char getFirstChar(string str) {
            return str.ToCharArray()[0];
        }

上述方法,也可使用 Console.ReadKey().KeyChar 等待用户按下任意键,一次读入一个字符。就不用判断和处理输入一行的字符串,这里使用算是对上一节的巩固了吧!

switch(choice) {
                case "1": 
                    Console.Write($"{DateTime.Now}: 请输入要转换的字符:");
                    // 等待用户按下任意键,一次读入一个字符。
                    char ch = Console.ReadKey().KeyChar;
                    Console.WriteLine("\n字符 {0} 的ASCII码值为:{1}", ch, (int)ch);
                    break;
                case "2": 
                    ... ...
                    ... ...
            }

输入转换字符后,直接强转成 ASCII 码比较简单,然后就是将输入的 ASCII 码值字符串在合法的规定数值范围内转成 int 类型,如果 ASCII 码值在 32~127 之间直接使用 Covert 类进行转换就好了,如果在范围之外,那么就是控制字符了,为了能直观呈现对应的控制字符,初始化一张字典表,通过 ASCII 码值作为键获取对应控制字符字符串值。

switch(choice) {
                case "1": 
                    ... ...
                    ... ...
                case "2": 
                    int asc = readAscValue();
                    Console.WriteLine("ASCII码值 {0} 对应的字符为:{1}", asc, asc < 127 && asc > 32 ? Convert.ToChar(asc) : operationalCharactersDic[asc]);
                    break;
            }

方法:返回读取到的且在 0~127 范围的 ASCII 码值

        /// <summary>
        /// 返回读取到的且在0~127范围的ASCII码值
        /// </summary>
        /// <returns>ASCII码值</returns>
        static int readAscValue() {
            int number = 0;
            do {
                Console.Write($"{DateTime.Now}: 请输入ASCII码值:");
                try {
                    number = Convert.ToInt32(Console.ReadLine()); // number = System.Int32.Parse(Console.ReadLine());
                    if (number < 0 || number > 127) {
                        Console.WriteLine($"{DateTime.Now}: 超出ASCII码值范围,请重新输入!");
                        continue;
                    }
                    return number;
                }
                catch {
                    Console.WriteLine($"{DateTime.Now}: 输入有误,请重新输入!");
                }
            } while (true);
        }

3.2.1 运行

image.png

3.3 操作符练习

运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。

C# 有丰富的内置运算符,分类如下:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其他运算符
namespace demo.operatorDemo
{
    /// <summary>
    /// 操作符学习
    /// </summary>
    class OperatorLearning
    {
        /// <summary>
        /// 将秒数转成多少天多少小时多少分钟和多少秒
        /// </summary>
        /// <param name="seconds">秒数</param>
        /// <returns>转换后的字符串</returns>
        public static string secondsTransform(int seconds)
        {
            int oneHour = 60 * 60; // 单位秒
            int oneDay = 24 * oneHour; // 单位秒
            // 天
            int dd = seconds / oneDay;
            // 小时
            int hh = (seconds % oneDay) / oneHour;
            // 分钟
            int mm = (seconds % oneHour) / 60;
            // 秒
            int ss = (seconds % oneHour) % 60;
            return seconds + "秒转换为:" + (dd < 10 ? ("0" + dd) : dd) + "天" +
                    (hh < 10 ? ("0" + hh) : hh) + "小时" +
                    (mm < 10 ? ("0" + mm) : mm) + "分钟" +
                    (ss < 10 ? ("0" + ss) : ss) + "秒";
        }

        /// <summary>
        /// 返回三个整型数中的最大数
        /// </summary>
        /// <param name="a">第一个数</param>
        /// <param name="b">第二个数</param>
        /// <param name="c">第三个数</param>
        /// <returns>最大数</returns>
        public static int largestInThreeNums(int a, int b, int c)
        {
            return a > b ? (a > c ? a : c) : (b > c ? b : c);
        }
    }
}

操作符练习 DEMO 中有两个方法 secondsTransform()largestInThreeNums() 分别对应的功能是将传入的秒数转成多少天多少小时多少分钟和多少秒和返回三个形参整型数中的最大数,运用到了大部分操作运算符,大家可以通过上述例子得到很好的掌握!

3.3.1 运行

image.png

3.4 条件语句练习

C# 提供了以下类型的判断语句:

语句描述
if 语句一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。
if...else 语句一个 if 语句 后可跟一个可选的 else 语句else 语句在布尔表达式为假时执行。
嵌套 if 语句您可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。
switch 语句一个 switch 语句允许测试一个变量等于多个值时的情况。
嵌套 switch 语句您可以在一个 switch 语句内使用另一个 switch 语句。

除此之外,我们还可以使用 ? : 运算符

在前面的示例代码中我们已经非常熟悉 条件运算符 ? : 了,可以用来替代 if...else 语句。它的一般形式如下:

Exp1 ? Exp2 : Exp3;

其中,Exp1Exp2Exp3 是表达式。请注意,冒号的使用和位置。

? 表达式的值是由 Exp1 决定的。如果 Exp1 为真,则计算 Exp2 的值,结果即为整个 ? 表达式的值。如果 Exp1 为假,则计算 Exp3 的值,结果即为整个 ? 表达式的值。

namespace demo.conditionalDemo
{
    /// <summary>
    /// 条件判断语句学习
    /// </summary>
    class Conditional
    {
        /// <summary>
        /// 返回分数所对应的评价等级(if-else)
        /// </summary>
        /// <param name="score">分数</param>
        /// <returns>评价等级</returns>
        public static string getScoreDegree1(float score)
        {
            Console.WriteLine($"随机分数:{score} 分");
            string degree;
            if (score >= 90f)
            {
                degree = "优秀";
            }
            else if (score >= 80f)
            {
                degree = "良好";
            }
            else if (score >= 60f)
            {
                degree = "及格";
            }
            else
            {
                degree = "不及格";
            }
            return degree;
        }

        /// <summary>
        /// 返回分数所对应的评价等级(switch-case)
        /// </summary>
        /// <param name="score">分数</param>
        /// <returns>评价等级</returns>
        public static string getScoreDegree2(float score)
        {
            Console.WriteLine($"随机分数:{score} 分");
            string degree;
            switch ((int)(score / 10))
            {
                case 10:
                case 9:
                    degree = "优秀";
                    break;
                case 8:
                    degree = "良好";
                    break;
                case 7:
                case 6:
                    degree = "及格";
                    break;
                default:
                    degree = "不及格";
                    break;
            }
            return degree;
        }

        /// <summary>
        /// 随机模拟分数(0~100存在0.5分半分)
        /// </summary>
        /// <returns>分数</returns>
        public static float mockScore()
        {
            Random r = new Random();
            // 小数点前
            string beforePoint = r.Next(0, 101).ToString();
            // 小数点后
            string afterPoint = r.Next(0, 9).ToString();
            // 前后拼接成小数
            string combined = beforePoint + "." + (int.Parse(beforePoint) == 100 || int.Parse(afterPoint) < 5 ? "0" : "5");
            // 转换得到随机小数
            return float.Parse(combined);
        }
    }
}

这个示例代码中有两个获取分数所对应的评价等级的方法,一个方法使用的是 if...else 判断另一个则使用 switch 语句,将模拟出来的随机分数传入两个方法当中,通过判断分数所处区间返回对应的分数等级。

再来一个例子:输入年和月份返回该年该月的天数(平闰年判断)

        /// <summary>
        /// 输入年和月份返回该年该月的天数(平闰年判断)
        /// </summary>
        public static void getMonthsOfRandomYear()
        {
            Console.Write($"{DateTime.Now}: 请输入年份:");
            try
            {
                int year = Convert.ToInt32(Console.ReadLine());
                Console.Write($"{DateTime.Now}: 请输入月份:");
                try
                {
                    int month = Convert.ToInt32(Console.ReadLine());
                    if (month >= 1 && month <= 12)
                    {
                        int day = 0;
                        bool isLeapYear = false;
                        switch (month)
                        {
                            case 1:
                            case 3:
                            case 5:
                            case 7:
                            case 8:
                            case 10:
                            case 12:
                                day = 31;
                                break;
                            case 2:
                                if ((year % 400 == 0) || (year / 4 == 0 && year % 100 != 0))
                                {
                                    // 闰年2月是29天
                                    day = 29;
                                    isLeapYear = true;
                                }
                                else
                                {
                                    // 平年2月是28天
                                    day = 28;
                                }
                                break;
                            default:
                                day = 30;
                                break;
                        }
                        Console.WriteLine("{0}: {1}年{2}月有{3}天,这一年是{4}", DateTime.Now, year, month, day, isLeapYear ? "闰年" : "平年");
                    }
                    else
                    {
                        Console.WriteLine($"{DateTime.Now}: 你家日历有 {month} 月啊?");
                    }
                }
                catch
                {
                    Console.WriteLine($"{DateTime.Now}: 输入月份错误!");
                }
            }
            catch
            {
                Console.WriteLine($"{DateTime.Now}: 输入年份错误!");
            }
        }

判断用户输入时用到了异常处理与捕获,可以省去,这个后续会展开,这里仅作了解即可!这里使用到了 if 语句中嵌套 switch 语句中嵌套 if...else 语句,多重嵌套看似复杂其实剖开分析并不难, 平闰年仅二月天数有相差,其余要么三十天要么三十一天。

3.4.1 运行

image.png

3.5 循环语句练习

C# 提供了以下几种循环类型:

循环类型描述
while 循环当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。
for/foreach 循环多次执行一个语句序列,简化管理循环变量的代码。
do...while 循环除了它是在循环主体结尾测试条件外,其他与 while 语句类似。
嵌套循环您可以在 whilefordo..while 循环内使用一个或多个循环。

循环控制语句更改执行的正常序列。当执行离开一个范围时,所有在该范围中创建的自动对象都会被销毁。

C# 提供了下列的控制语句:

控制语句描述
break 语句终止 loop 或 switch 语句,程序流将继续执行紧接着 loopswitch 的下一条语句。
continue 语句引起循环跳过主体的剩余部分,立即重新开始测试条件。
using System.Numerics;

namespace demo.loopDemo
{
    /// <summary>
    /// 循环语句学习
    /// </summary>
    class Loop
    {

        /// <summary>
        /// 从1累加到100
        /// 1 + 2 + 3 + ... + 100
        /// </summary>
        public static void plusTo100()
        {
            // 累加器
            int sum = 0;
            int num = 1;
            while (num <= 100)
            {
                sum += num;
                num++;
            }
            Console.WriteLine("1 + 2 + 3 + ... + 100 = {0}", sum);
        }

        /// <summary>
        /// 从1累加到100
        /// 1 × 2 × 3 × ... × 50
        /// </summary>
        public static void multiplyTo100()
        {
            // 累乘器
            BigInteger product = BigInteger.One;
            BigInteger num = BigInteger.Parse("2");
            do
            {
                product *= num;
                num++;
            } while (num.CompareTo(50) <= 0);
            Console.WriteLine($"1 × 2 × 3 × ... × 50 = {product}");
        }
        ... ...
        ... ...
    }
}

使用 while 循环实现累加和累乘,for 循环也是一样的( ̄▽ ̄)"

值得注意的是,前 100 内的累加结果的可以用 int ,但是累乘的话会溢出,这里可以使用 BigInteger 大整型来表示更大范围的数字。

... ...
... ...

        /// <summary>
        /// [100,1000)内寻找水仙花数
        /// 1^3 + 5^3 + 3^3 = 153
        /// </summary>
        public static void findNarcissisticNumber()
        {
            int count = 0;
            for (int i = 100; i < 1000; i++)
            {
                // 百位
                int h = i / 100;
                // 十位
                int d = i / 10 % 10;
                // 个位
                int u = i % 10;
                if (Math.Pow(h, 3) + Math.Pow(d, 3) + Math.Pow(u, 3) == i)
                {
                    Console.WriteLine(i);
                    count++;
                }
            }
            Console.WriteLine("[100,1000)范围内共有水仙花数 {0} 个", count);
        }
        ... ...
        ... ...

[100,1000) 内寻找水仙花数,使用运算符得到三位数的百位、十位和个位,判断统计水仙花个数。

... ...
... ...
        /// <summary>
        /// 打印矩形九九乘法表
        /// </summary>
        public static void printRectangularMultiplicationTable()
        {
            for (int i = 1; i <= 9; i++)
            {
                for (int j = 1; j <= 9; j++)
                {
                    Console.Write($"{j} × {i} = {i * j}\t");
                }
                Console.WriteLine();
            }
        }
        
        /// <summary>
        /// 打印金字塔状九九乘法表
        /// </summary>
        public static void printPyramidMultiplicationTable()
        {
            for (int i = 1; i <= 9; i++)
            {
                for (int j = 1; j <= i; j++)
                {
                    Console.Write($"{j} × {i} = {i * j}\t");
                }
                Console.WriteLine();
            }
        }

        /// <summary>
        /// 打印倒金字塔状九九乘法表
        /// </summary>
        public static void printReversePyramidMultiplicationTable()
        {
            for (int i = 9; i >= 1; i--)
            {
                for (int j = 1; j <= i; j++)
                {
                    Console.Write($"{j} × {i} = {i * j}\t");
                }
                Console.WriteLine();
            }
        }
        ... ...
        ... ...

无论是打印矩形、金字塔状还是倒金字塔状九九乘法表,都需要用到两个 for 循环,注意控制行列的个数。

... ...
... ...
        /// <summary>
        /// 找出[2,endNum]内的所有质数(素数)
        /// </summary>
        /// <param name="endNum">终止数</param>
        public static void findPrimeNumbers(int endNum)
        {
            if (endNum >= 2)
            {
                int count = 0;
                for (int i = 2; i <= endNum; i++)
                {
                    bool flag = true;
                    for (int j = 2; j < i; j++)
                    {
                        if (i % j == 0)
                        {
                            flag = false; 
                            break;
                        }
                    }
                    if (flag)
                    {
                        count++;
                        Console.Write(i);
                    }
                }
                Console.WriteLine($"\n[2,{endNum}]范围内共有质数(素数) {count} 个");
            }
        }
        ... ...
        ... ...

找出 [2,终止数] 内的所有质数(素数),质数是指在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然数。

3.5.1 运行

image.png

四、结尾

撰文不易,欢迎大家点赞、评论,你的关注、点赞是我坚持的不懈动力,感谢大家能够看到这里!Peace & Love。