C#理论 —— 基础语法、数据类型、变量、常量、运算符、三大结构

文章目录

  • 1. 基础语法
    • 1.1 标识符命名规则
    • 1.2 C# 关键字
    • 1.3 C#注释
  • 2. 数据类型
    • 2.1 值类型(Value types)
    • 2.2 引用类型(Reference types)
      • 2.2.1 对象(Object)类型
      • 3.2.2 动态(Dynamic)类型
      • 2.2.3 字符串(String)类型
        • 2.2.3.1 关于@
    • 2.3 指针类型(Pointer types)
    • 2.4 数据类型转换
      • 2.4.1 内置的类型转换方法
    • 2.5 可空类型(Nullable)
      • 2.5.1 单问号 ? 与 双问号 ??
    • 2.6 数组(Array)
      • 2.6.1 声明/初始化/赋值数组
      • 2.6.2 访问数组元素
      • 2.6.3 使用 foreach 循环遍历数组
      • 2.6.4 多维数组
    • 2.7 字符串(String)
      • 2.7.1 创建 String 对象
      • 2.7.2 String 类的属性
      • 2.7.3 String 类的方法
      • 2.7.* 例程
        • 2.7.*.1 比较字符串
        • 2.7.*.2 查找字符串中指定的字符串
        • 2.7.*.3 获取子字符串(切片)
        • 2.7.*.4 拼接字符串
    • 2.8 结构体(Struct)
      • 2.8.1 定义结构体/结构体变量
      • 2.8.2 结构体与类的区别
    • 2.9 枚举(Enum)
    • 2.10 数据类型转换
      • string 相关的转化
  • 3. 变量
    • 3.1 变量类型转换
    • 3.2 强制类型转换
    • 3.3 变量作用域
    • 3.4 变量修饰符
      • 3.4.1 `out`修饰符展示
      • 3.4.2 `ref`修饰符展示
  • 4. 常量
    • 4.1 整型常量
    • 4.2 浮点型常量
    • 4.3 字符型常量
    • 4.4 字符串型常量
    • 4.5 常量的定义
  • 5. 运算符
    • 5.1 算术运算符
      • 5.1.1 关于i++与++i
    • 5.2 关系运算符
    • 5.3 逻辑运算符
    • 5.4 位运算符
    • 5.5 赋值运算符
    • 5.6 其他运算符
    • 5.7 运算符优先级
  • 6. 三大结构
    • 6.1 条件结构
      • 6.1.1 switch语句
      • 6.1.2 条件运算符 ?:
    • 6.2 循环结构
      • 6.2.1 for/foreach 循环
      • 6.2.2 循环控制语句

1. 基础语法


1.1 标识符命名规则

标识符是用来识别类、变量、函数或任何其它用户定义的项目(即变量名、函数名、类名等)。在 C# 中,类的命名必须遵循如下基本规则:

  • 必须以字母、下划线或 @ 开头。第一个字符不能是数字。
  • 必须不包含任何嵌入的空格或符号,比如 ? - +! # % ^ & * ( ) [ ] { } . ; : " ’ / \。
  • 不能是 C# 关键字。除非它们有一个 @ 前缀。 例如,@if 是有效的标识符,但 if 不是,因为 if 是关键字。
  • 必须区分大小写。大写字母和小写字母被认为是不同的字母。
  • 不能与C#的类库名称相同。

1.2 C# 关键字

关键字是 C# 编译器预定义的保留字。这些关键字不能用作标识符。有些关键字在代码的上下文中有特殊的意义,如 getset,这些被称为上下文关键字(Contextual keywords)。

  • C#关键字

1.3 C#注释

文档注释只需在方法或类定义的上一行输入///即可自动生成注释模板。一般文档注释也只用在方法或类上。

// 单行注释

/*
多
行
注
释
*/

///<summary>
///文档注释
///</summary>

2. 数据类型


在 C# 中,变量分为以下几种类型:

  • 值类型(Value types)
  • 引用类型(Reference types)
  • 指针类型(Pointer types)

2.1 值类型(Value types)

值类型数据可以直接分配给其一个值。它们是从类 System.ValueType 中派生的。
被赋值后的值类型变量就包含了数据。比如int、char、float等数据类型,它们分别存储数字、字符、浮点数。当您声明一个 int 类型的变量时,系统分配内存来存储该变量的值。

  • 值类型

2.2 引用类型(Reference types)

引用类型不包含存储在变量中的实际数据,但包含对变量的引用。即它们指向的是一个内存位置。内置的引用类型有:objectdynamicstring

  • 应用类型

2.2.1 对象(Object)类型

该类型是C# 通用类型系统(Common Type System - CTS)中所有数据类型的最终基类。Object 是 System.Object 类的别名。所以对象(Object)类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。

当一个值类型转换为对象类型时,则被称为装箱;反之,当一个对象类型转换为值类型时,则被称为拆箱

object obj; // 声明对象类型变量
obj = 100; // 装箱

3.2.2 动态(Dynamic)类型

该类型可存储任何类型的值在动态数据类型变量中。这些变量的类型定义在运行时发生。

动态类型与对象类型相似,但对象类型变量的类型定义是在编译时发生的,而动态类型变量的类型定义是在运行时发生的。

dynamic d = 20; // 声明并初始化动态类型变量

2.2.3 字符串(String)类型

该类型允许给变量分配任何字符串值。字符串(String)类型是System.String 类的别名。它从对象(Object)类型派生的。字符串(String)类型的值可以通过两种形式进行分配:引号""和 @引号@""

String str = "run"; // 声明并初始化字符串类型变量
@"runoob.com"; // @引号字符串
2.2.3.1 关于@

@逐字字符串,其作用如下:

  • 可将转义字符\当作普通字符对待。
  • @ 字符串中可以任意换行,换行符及缩进空格都计算在字符串长度之内。
string str = @"C:\Windows";
等价于:
string str = "C:\\Windows";

string str = @"<script type=""text/javascript"">
    <!--
    -->
</script>";

2.3 指针类型(Pointer types)

指针类型存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。

char* cptr; //声明指针变量
int* iptr;

2.4 数据类型转换

C# 中,数据类型转换有两种形式:

  • 隐式类型转换:C# 默认的自动的以安全方式进行的转换, 它不会导致数据的丢失。例如,从小的整数类型转换为大的整数类型,从派生类转换为基类。
  • 显式类型转换:即强制类型转换。显式转换需要强制转换运算符,强制转换会造成数据的丢失。
using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Text;
// using System.Threading.Tasks;

namespace TypeConversionApplication
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            double d = 5673.74;
            int i;

            
            i = (int)d; // 强制转换 double 为 int类型
            Console.WriteLine(i);
            Console.ReadKey();

        }
    }
}

控制台输出:5673

2.4.1 内置的类型转换方法

方法命功能描述
ToBoolean如果可能的话,把类型转换为布尔型。
ToByte把类型转换为字节类型。
ToChar如果可能的话,把类型转换为单个 Unicode 字符类型。
ToDateTime把类型(整数或字符串类型)转换为 日期-时间 结构。
ToDecimal把浮点型或整数类型转换为十进制类型。
ToDouble把类型转换为双精度浮点型。
ToInt16把类型转换为 16 位整数类型。
ToInt32把类型转换为 32 位整数类型。
ToInt64把类型转换为 64 位整数类型。
ToSbyte把类型转换为有符号字节类型。
ToSingle把类型转换为小浮点数类型。
ToString把类型转换为字符串类型。
ToType把类型转换为指定类型。
ToUInt16把类型转换为 16 位无符号整数类型。
ToUInt32把类型转换为 32 位无符号整数类型。
ToUInt64把类型转换为 64 位无符号整数类型。
using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Text;
// using System.Threading.Tasks;

namespace TypeConversionApplication
{
    class StringConversion
    {
        static void Main(string[] args)
        {
            int i = 75;
            float f = 53.005f;
            double d = 2345.7652;
            bool b = true;

            Console.WriteLine(i.ToString());
            Console.WriteLine(f.ToString());
            Console.WriteLine(d.ToString());
            Console.WriteLine(b.ToString());
            Console.ReadKey();

        }
    }
}

2.5 可空类型(Nullable)

C# 提供了一个特殊的数据类型,nullable 类型(可空类型),被定义为可空类型的变量允许表示其基础值类型正常范围内的所有值或 null 值。

例如,Nullable< Int32 >,读作"可空的 Int32",它可被赋值为 -2,147,483,648 到 2,147,483,647 之间的任意值,也可被赋值为 null 值。类似的,Nullable< bool > 变量可以被赋值为 true 或 false 或 null。

在处理数据库和其他包含可能未赋值的元素的数据类型时,将 null 赋值给数值类型或布尔型的功能特别有用。

例如,数据库中的布尔型字段可以存储值 true 或 false,或该字段也可以未定义,即null。

using System;
namespace CalculatorApplication
{
   class NullablesAtShow
   {
      static void Main(string[] args)
      {
         int? num1 = null; // 定义可空的整数型变量
         int? num2 = 45;
         double? num3 = new double?(); // 定义可空的双精度浮点型变量
         double? num4 = 3.14157;
         
         bool? boolval = new bool?(); // 定义可空的布尔型变量

         // 显示值
         
         Console.WriteLine("显示可空类型的值: {0}, {1}, {2}, {3}", 
                            num1, num2, num3, num4);
         Console.WriteLine("一个可空的布尔值: {0}", boolval);
         Console.ReadLine();

      }
   }
}

2.5.1 单问号 ? 与 双问号 ??

单问号?用于对 int,double,bool 等无法直接赋值为 null 的数据类型进行 null 的赋值,即这个数据类型是 Nullable 类型的。

int i; //默认值为0
int? ii; //默认值为null

双问号??又叫合并运算符,可用于判断一个变量是否为 null ,是则返回一个指定的值。

double? num1 = null;
double? num2 = 3.14157;
double num3;
num3 = num1 ?? 5.34;      // num1 如果为空值则返回 5.34,否则返回num1的值
Console.WriteLine("num3 的值: {0}", num3);
num3 = num2 ?? 5.34; // num2 如果为空值则返回 5.34,否则返回num2的值,即3.14157
Console.WriteLine("num3 的值: {0}", num3);

运行结果:
num3 的值: 5.34
num3 的值: 3.14157

2.6 数组(Array)

数组是一个存储相同类型元素的固定大小的有序的集合。

数组是由连续的内存位置组成的。最低的地址对应第一个元素,最高的地址对应最后一个元素。

2.6.1 声明/初始化/赋值数组

数组是一个引用类型,需要使用 new 关键字来创建数组的实例。

声明数组后,内存中不会为其分配内粗。只有当数组被初始化后,才会为其分配内存,此后才能给数组赋值。也可以在声明数组的同时,对数组进行赋值(相当于初始化与赋值同步进行)。

数组之间赋值时,目标和源都会指向相同的内存位置。

double[] balance; // 声明数组
double[] balance = new double[10]; // 声明并初始化数组
balance[0] = 4500.0; // 数组赋值

double[] balance = {2340.0, 4523.69, 3421.0}; // 声明数组的同时赋值数组
int [] marks = new int[5]  { 99,  98, 92, 97, 95}; // 同时声明、初始化、赋值数组。其中数组的大小可以省略。
int[] score = marks; // 赋值一个数组变量到另一个数组

2.6.2 访问数组元素

元素是通过带索引的数组名称来访问的。

double salary = balance[9];

2.6.3 使用 foreach 循环遍历数组

using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Text;
// using System.Threading.Tasks;
namespace ArrayApplication
{
    class MyArray
    {
        static void Main(string[] args)
        {
            int[] n = new int[10]; /* n 是一个带有 10 个整数的数组 */


            /* 初始化数组 n 中的元素 */
            for (int i = 0; i < 10; i++)
            {
                n[i] = i + 100;
            }

            /* 输出每个数组元素的值 */
            foreach (int j in n)
            {
                int i = j - 100;
                Console.WriteLine("Element[{0}] = {1}", i, j);
            }
            Console.ReadKey();
        }
    }
}

2.6.4 多维数组

多维数组又称为矩形数组。一个二维数组,在本质上,是一个一维数组的列表。

string [,] names; // 声明一个二维数组
int [ , , ] m; // 声明一个三维数组
  • 初始化二维数组:多维数组可以通过在括号内为每行指定值来进行初始化。
int [,] a = new int [3,4] {
 {0, 1, 2, 3} ,   /*  初始化索引号为 0 的行 */
 {4, 5, 6, 7} ,   /*  初始化索引号为 1 的行 */
 {8, 9, 10, 11}   /*  初始化索引号为 2 的行 */
};
  • 访问二维数组元素
int val = a[2,3];

2.7 字符串(String)

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

2.7.1 创建 String 对象

可以使用以下方法之一来创建 string 对象:

  • 通过给 String 变量赋值一个字符串
  • 通过使用 String 类构造函数
  • 通过使用字符串串联运算符( + )
  • 通过检索属性或调用一个返回字符串的方法
  • 通过格式化方法来转换一个值或对象为它的字符串表示形式
using System;

namespace StringApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            //字符串,字符串连接
            string fname, lname; // 定义string变量
            fname = "an"; 
            lname = "tennin";

            string fullname = fname + lname;
            Console.WriteLine("Full Name: {0}", fullname);

            //通过使用 string 构造函数
            char[] letters = { 'H', 'e', 'l', 'l', 'o' }; // 定义并初始化字符数组
            string greetings = new string(letters); 
            Console.WriteLine("Greetings: {0}", greetings);

            //方法返回字符串
            string[] sarray = { "Hello", "From", "antennin", "Point" }; // 定义并初始化字符串数组
            string message = String.Join(" ", sarray); // 使用Join方法插入字符串
            Console.WriteLine("Message: {0}", message);

            //用于转化值的格式化方法
            DateTime waiting = new DateTime(2012, 10, 10, 17, 58, 1);
            string chat = String.Format("Message sent at {0:t} on {0:D}",waiting);
            Console.WriteLine("Message: {0}", chat);
            Console.ReadKey();
        }
    }
}

运行输出:
Full Name: antennin
Greetings: Hello
Message: Hello From antennin Point
Message: Message sent at 17:58 on 20121010

2.7.2 String 类的属性

String 类有两个属性:

属性名称描述
Chars在当前 String 对象中获取 Char 对象的指定位置。
Length在当前的 String 对象中获取字符数。

2.7.3 String 类的方法

下面的提供了一些最常用的string方法:

转到函数的定义处即可查看到该函数的重载和官方应用说明。

方法名称描述
public static int Compare( string strA, string strB )比较两个指定的 string 对象,并返回一个表示它们在排列顺序中相对位置的整数。该方法区分大小写。
public static int Compare( string strA, string strB, bool ignoreCase )比较两个指定的 string 对象,并返回一个表示它们在排列顺序中相对位置的整数。但是,如果布尔参数为真时,该方法不区分大小写。
public static string Concat( string str0, string str1 )连接两个 string 对象。
public static string Concat( string str0, string str1, string str2 )连接三个 string 对象。
public static string Concat( string str0, string str1, string str2, string str3 )连接四个 string 对象。
public bool Contains( string value )返回一个表示指定 string 对象是否出现在字符串中的值。
public static string Copy( string str )创建一个与指定字符串具有相同值的新的 String 对象。
public void CopyTo( int sourceIndex, char[] destination, int destinationIndex, int count )从 string 对象的指定位置开始复制指定数量的字符到 Unicode 字符数组中的指定位置。
public bool EndsWith( string value )判断 string 对象的结尾是否匹配指定的字符串。
public bool Equals( string value )判断当前的 string 对象是否与指定的 string 对象具有相同的值。
public static bool Equals( string a, string b )判断两个指定的 string 对象是否具有相同的值。
public static string Format( string format, Object arg0 )把指定字符串中一个或多个格式项替换为指定对象的字符串表示形式。
public int IndexOf( char value )返回指定 Unicode 字符在当前字符串中第一次出现的索引,索引从 0 开始。
public int IndexOf( string value )返回指定字符串在该实例中第一次出现的索引,索引从 0 开始。
public int IndexOf( char value, int startIndex )返回指定 Unicode 字符从该字符串中指定字符位置开始搜索第一次出现的索引,索引从 0 开始。
public int IndexOf( string value, int startIndex )返回指定字符串从该实例中指定字符位置开始搜索第一次出现的索引,索引从 0 开始。
public int IndexOfAny( char[] anyOf )返回某一个指定的 Unicode 字符数组中任意字符在该实例中第一次出现的索引,索引从 0 开始。
public int IndexOfAny( char[] anyOf, int startIndex )返回某一个指定的 Unicode 字符数组中任意字符从该实例中指定字符位置开始搜索第一次出现的索引,索引从 0 开始。
public string Insert( int startIndex, string value )返回一个新的字符串,其中,指定的字符串被插入在当前 string 对象的指定索引位置。
public static bool IsNullOrEmpty( string value )指示指定的字符串是否为 null 或者是否为一个空的字符串。
public static string Join( string separator, string[] value )连接一个字符串数组中的所有元素,使用指定的分隔符分隔每个元素。
public static string Join( string separator, string[] value, int startIndex, int count )连接接一个字符串数组中的指定位置开始的指定元素,使用指定的分隔符分隔每个元素。
public int LastIndexOf( char value )返回指定 Unicode 字符在当前 string 对象中最后一次出现的索引位置,索引从 0 开始。
public int LastIndexOf( string value )返回指定字符串在当前 string 对象中最后一次出现的索引位置,索引从 0 开始。
public string Remove( int startIndex )移除当前实例中的所有字符,从指定位置开始,一直到最后一个位置为止,并返回字符串。
public string Remove( int startIndex, int count )从当前字符串的指定位置开始移除指定数量的字符,并返回字符串。
public string Replace( char oldChar, char newChar )把当前 string 对象中,所有指定的 Unicode 字符替换为另一个指定的 Unicode 字符,并返回新的字符串。
public string Replace( string oldValue, string newValue )把当前 string 对象中,所有指定的字符串替换为另一个指定的字符串,并返回新的字符串。
public String[] Split(String[] separator, StringSplitOptions options);基于数组separator中的字符串将字符串拆分为多个子字符串。
public String[] Split(String[] separator, int count, StringSplitOptions options);基于数组separator中的字符将一个字符串拆分成最大数量的子字符串。
public bool StartsWith( string value )判断字符串实例的开头是否匹配指定的字符串。
public char[] ToCharArray()返回一个带有当前 string 对象中所有字符的 Unicode 字符数组。
public char[] ToCharArray( int startIndex, int length )返回一个带有当前 string 对象中所有字符的 Unicode 字符数组,从指定的索引开始,直到指定的长度为止。
public string ToLower()把字符串转换为小写并返回。
public string ToUpper()把字符串转换为大写并返回。
public string Trim()移除当前 String 对象中的所有前导空白字符和后置空白字符。

2.7.* 例程

2.7.*.1 比较字符串
using System;

namespace StringApplication
{
   class StringProg
   {
      static void Main(string[] args)
      {
         string str1 = "This is test";
         string str2 = "This is text";

         if (String.Compare(str1, str2) == 0)
         {
            Console.WriteLine(str1 + " and " + str2 +  " are equal.");
         }
         else
         {
            Console.WriteLine(str1 + " and " + str2 + " are not equal.");
         }
         Console.ReadKey() ;
      }
   }
}
  • 也可以通过运算符比较两个字符串
using System;

namespace StringApplication
{
   class StringProg
   {
      static void Main(string[] args)
      {
         string str1 = "This is test";
         string str2 = "This is text";

         if (str1 == str2)
         {
            Console.WriteLine(str1 + " and " + str2 +  " are equal.");
         }
         else if(str1 != str2)
         {
            Console.WriteLine(str1 + " and " + str2 + " are not equal.");
         }
         Console.ReadKey() ;
      }
   }
}
2.7.*.2 查找字符串中指定的字符串
using System;

namespace StringApplication
{
    class StringProg
    {
        static void Main(string[] args)
        {
            string str = "This is test";
            if (str.Contains("test"))
            {
                Console.WriteLine("The sequence 'test' was found.");
            }
            Console.ReadKey();
        }
    }
}
2.7.*.3 获取子字符串(切片)
using System;
namespace StringApplication
{
    class StringProg
    {
        static void Main(string[] args)
        {
            string str = "Last night I dreamt of San";
            Console.WriteLine(str);
            string substr = str.Substring(23); // 获取子字符串
            Console.WriteLine(substr);
            Console.ReadKey();
        }
    }
}
2.7.*.4 拼接字符串
using System;

namespace StringApplication
{
    class StringProg
    {
        static void Main(string[] args)
        {
            string[] starray = new string[]{"Down the way nights are dark",
                                             "And the sun shines daily on the mountain top",
                                             "I took a trip on a sailing ship",
                                             "And when I reached Jamaica",
                                             "I made a stop"};

            string str = String.Join("\n", starray); // 插入换行符
            Console.WriteLine(str);
            Console.ReadKey();
        }   
    }
}

2.8 结构体(Struct)

在 C# 中,结构体属于值类型数据结构。它可以存储各种数据类型的相关数据。这一点与C/C++原理相近。

2.8.1 定义结构体/结构体变量

在 C# 中的结构体与 C/C++ 中的结构体不同的地方为:

  • 结构可带有方法、字段、索引、属性、运算符方法和事件。
  • 结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义无参构造函数。无参构造函数(默认)是自动定义的,且不能被改变。
  • 与类不同,结构不能继承其他的结构或类。
  • 结构不能作为其他结构或类的基础结构。
  • 结构可实现一个或多个接口。
  • 结构成员不能指定为 abstract、virtual 或 protected。
  • 当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
    -如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。
using System;
using System.Text;

// 定义结构体
struct Books
{
    public string title;
    public string author;
    public string subject;
    public int book_id;
};

public class testStructure
{
    public static void Main(string[] args)
    {
		// C#中定义结构体变量不需要前面加关键字 struct
        Books Book1;        /* 声明结构体变量 Book1,类型为 Books */
        Books Book2;        /* 声明结构体变量 Book2,类型为 Books */

        /* book 1 详述 */
        Book1.title = "C Programming";
        Book1.author = "Nuha Ali";
        Book1.subject = "C Programming Tutorial";
        Book1.book_id = 6495407;

        /* book 2 详述 */
        Book2.title = "Telecom Billing";
        Book2.author = "Zara Ali";
        Book2.subject = "Telecom Billing Tutorial";
        Book2.book_id = 6495700;

        /* 打印 Book1 信息 */
        Console.WriteLine("Book 1 title : {0}", Book1.title);
        Console.WriteLine("Book 1 author : {0}", Book1.author);
        Console.WriteLine("Book 1 subject : {0}", Book1.subject);
        Console.WriteLine("Book 1 book_id :{0}", Book1.book_id);

        /* 打印 Book2 信息 */
        Console.WriteLine("Book 2 title : {0}", Book2.title);
        Console.WriteLine("Book 2 author : {0}", Book2.author);
        Console.WriteLine("Book 2 subject : {0}", Book2.subject);
        Console.WriteLine("Book 2 book_id : {0}", Book2.book_id);

        Console.ReadKey();
    }
}

2.8.2 结构体与类的区别

类和结构有以下几个基本的不同点:

  • 类是引用类型,结构是值类型。
  • 结构不支持继承。
  • 结构不能声明默认的构造函数。
using System;
using System.Text;
     
struct Books
{
   private string title;
   private string author;
   private string subject;
   private int book_id;
   public void setValues(string t, string a, string s, int id)
   {
      title = t;
      author = a;
      subject = s;
      book_id =id; 
   }
   public void display()
   {
      Console.WriteLine("Title : {0}", title);
      Console.WriteLine("Author : {0}", author);
      Console.WriteLine("Subject : {0}", subject);
      Console.WriteLine("Book_id :{0}", book_id);
   }

};  

public class testStructure
{
   public static void Main(string[] args)
   {

      Books Book1 = new Books(); /* 声明 Book1,类型为 Books */
      Books Book2 = new Books(); /* 声明 Book2,类型为 Books */

      /* book 1 详述 */
      Book1.setValues("C Programming",
      "Nuha Ali", "C Programming Tutorial",6495407);

      /* book 2 详述 */
      Book2.setValues("Telecom Billing",
      "Zara Ali", "Telecom Billing Tutorial", 6495700);

      /* 打印 Book1 信息 */
      Book1.display();

      /* 打印 Book2 信息 */
      Book2.display(); 

      Console.ReadKey();

   }
}

2.9 枚举(Enum)

枚举是一组命名整型常量。与结构体一样,C# 的枚举属于值类型。即枚举包含自己的值,且不能继承或传递继承。

using System;

public class EnumTest
{
    enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat }; // 定义枚举

    static void Main()
    {
        int x = (int)Day.Sun;
        int y = (int)Day.Fri;
        Console.WriteLine("Sun = {0}", x);
        Console.WriteLine("Fri = {0}", y);
    }
}

2.10 数据类型转换

string 相关的转化

  1. intstring
int a = 15;
string s1 = a.ToString(); // 方法1
string s2 = Convert.ToString(a); // 方法2

3. 变量


一个变量是一个供程序操作的内存区的名字。在 C# 中,每个变量都有一个指定的类型,此类型决定了该变量的内存大小和布局。
C# 中提供的基本的变量类型可分为以下几类:

类型举例
整数类型sbyte、byte(0-255)、short(-32768-32767)、ushort、int(-2147483648-2147483647)、uint、long、ulong 和 char
浮点型float 和 double(double是所有类型中范围最大的)
十进制类型decimal,它也是浮点型的一种,精度比float和double高,float和double总是存在精度损失,而decimal则不会
布尔类型true 或 false
字符串类型string、char
空类型null,可为空值的数据类型
// 变量定义
int i, j, k;
char c, ch;
float f, salary;
double d;
// 变量定义并初始化
int i = 100;
float f = 1.1F; // 单精度浮点型数值必须加后缀F
double d = 1.1;
decimal de = 1.1M; // 十进制类型数值必须加后缀M
string s = "hellow world!"; // 字符串使用""包住
char c = 'h'; // 字符使用''包住

// null
string st = "hellow world!";
st = null;
st = "";
//以上都表示将字符串st清空,但不同的是null表示将字符串彻底清空,而""表示将一个空的字符串赋值给字符串st(空字符串也是一个字符串)
//变量类型中,只有string是可以为null的 

3.1 变量类型转换

  • 类型范围小的变量可赋值给类型范围大的变量,反之则报错。
// 变量定义
int i =0;
long l =0;

l=i; // 允许
i=l; // 报错
  • 浮点型的转换只有float赋值给double一种。

3.2 强制类型转换

3.3 变量作用域

  • 在函数内定义的局部变量只在其函数内作用。
  • 在类内函数外定义的变量可用在类内的函数总,但当该函数中存在同名变量时,则报错。

3.4 变量修饰符

  • 无修饰符:即变量类型无修饰符修饰。
  • out:传入参数必须由被调用的方法赋值修改,以此按引用传递。其原理近似于一个指针。
  • ref:传入参数在被方法调用前必须赋有初值,并输入参数在被调用的方法内可被修改也可不修改(数据是按引用传递的)。

  • outref的区别:
    • out 修饰的参数必须在方法内进行修改,而ref无该要求。
    • 调用方法的输入参数若是局部变量,out修饰的输入参数可无初始值,因为out一定会在方法内对其赋值。而ref修饰的输入参数只有在实参有初始值时才能被调用,因为ref不一定在方法内对其赋值。

3.4.1 out修饰符展示

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        { 
            string message = "hellow world!";
            MessageBox.Show(message);
            SendMesage(out message);
            MessageBox.Show(message);
        }

        public void SendMesage(out string message) // 输入变量为指向message变量的内存地址
        {
            message = "世界你好!"; // 该内存地址的值发生改变,out引用的参数必须在函数内进行赋值,这一点与ref不同
            MessageBox.Show(message);
        }
    }
}

运行结果:输出一次hellow world!,两次世界你好!
  • 另一种用法:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        { 
            SendMesage(out string message); // 在调用函数时定义变量,输入参数允许没有初始值,这一点与ref不同
            MessageBox.Show(message);
        }

        public void SendMesage(out string message)
        {
            message = "世界你好!";
            MessageBox.Show(message);
        }
    }
}


3.4.2 ref修饰符展示

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            string message = "hellow world!";
            SendMesage(ref message); // ref的输入参数必须要有初始值
            MessageBox.Show(message);
        }

        public void SendMesage(ref string message)
        {
            message = "世界你好!"; // ref的传入参数,在函数内可被修改,也可不被修改,这一点与out不同
            MessageBox.Show(message);
        }
    }
}

4. 常量


常量是固定值,在程序执行期间不会也不能改变。常量可以是任何基本数据类型,比如整数常量、浮点常量、字符常量或者字符串常量,还有枚举常量。

4.1 整型常量

整数常量可以是十进制、八进制或十六进制的常量。
其前缀指定了其进制类型:0x 或 0X 表示十六进制,0 表示八进制,没有前缀则表示十进制。

其后缀制定了其数据类型,可以是 U 或 L ,其中,U 和 L 分别表示 unsigned 和 long。可以多个后缀以任意顺序进行组合。后缀部分大小写。

212         /* 合法 */
215u        /* 合法 */
0xFeeL      /* 合法 */
078         /* 非法:8 不是一个八进制数字 */
032UU       /* 非法:不能重复后缀 */

85         /* 十进制 */
0213       /* 八进制 */
0x4b       /* 十六进制 */
30         /* int */
30u        /* 无符号 int */
30l        /* long */
30ul       /* 无符号 long */

4.2 浮点型常量

一个浮点常量是由整数部分、小数点、小数部分和指数部分组成。可使用小数形式或指数形式来表示浮点常量。

3.14159       /* 合法 */
314159E-5L    /* 合法 */
210f          /* 非法:没有小数或指数 */
.e55          /* 非法:缺少整数或小数 */

使用小数形式表示时,必须包含小数点或指数或同时包含两者。
使用指数形式表示时,必须包含整数部分或小数部分或同时包含两者。

4.3 字符型常量

字符常量括在单引号里,如'x'。一个字符常量可以是一个普通字符(如 'x')或一个转义序列(如 '\t')或一个通用字符(如 '\u02C0')。

在 C# 中有一些特定的字符,当它们的前面带有反斜杠时有特殊的意义,可用于表示换行符(\n)或制表符 tab(\t)。

转义序列码有:

转义序列含义
| \ 字符
’ 字符
"" 字符
?? 字符
\aAlert 或 bell
\b退格键(Backspace)
\f换页符(Form feed)
\n换行符(Newline)
\r回车
\t水平制表符 tab
\v垂直制表符 tab
\ooo一到三位的八进制数
\xhh . . .一个或多个数字的十六进制数

4.4 字符串型常量

字符串常量括在双引号 "" 里,或括在 @"" 里。字符串常量包含的字符与字符常量相似,可以是:普通字符、转义序列和通用字符

字符串常量可把一个很长的行拆成多个行,可以使用空格分隔各个部分。

string a = "hello, world";                  
string b = @"hello, world";             
string j = @"one
                        two
                           three";

4.5 常量的定义

使用 关键字const 来定义常量 。

const int c1 = 5;

5. 运算符


C# 内置的运算符分类如下:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其他运算符

5.1 算术运算符

运算符描述
+把两个操作数相加
-从第一个操作数中减去第二个操作数
*把两个操作数相乘
/分子除以分母
%取模运算符,整除后的余数
++自增运算符,整数值增加 1
自减运算符,整数值减少 1

5.1.1 关于i++与++i

与C、C++语法同理:

  • c = i++;:先将 i赋值给 c,再对 i 进行自增运算。
  • c = ++i; :先将 i 进行自增运算,再将 i 赋值给 c 。

5.2 关系运算符

运算符描述
==检查两个操作数的值是否相等,如果相等则条件为真。
!=检查两个操作数的值是否相等,如果不相等则条件为真。
>检查左操作数的值是否大于右操作数的值,如果是则条件为真。
<检查左操作数的值是否小于右操作数的值,如果是则条件为真。
>=检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。
<=检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。

5.3 逻辑运算符

运算符描述
&&逻辑与运算符。如果两个操作数都非零,则条件为真。
`
!逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。

5.4 位运算符

运算符作用于逐位执行操作。
以下假设 A = 0011 1100、B = 0000 1101

运算符描述实例
&位与,如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。(A & B) 将得到 12,即为 0000 1100
``位或,如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中。
^异或运算符,当两个二进制数不同时,结果为1。(A ^ B) 将得到 49,即为 0011 0001
~按位取反运算符是一元运算符,即0变成1,1变成0,包括符号位。(~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。
<<二进制左移运算符。左操作数的值向左移动右操作数指定的位数。A << 2 将得到 240,即为 1111 0000
>>二进制右移运算符。左操作数的值向右移动右操作数指定的位数。A >> 2 将得到 15,即为 0000 1111

5.5 赋值运算符

运算符描述实例
=简单的赋值运算符,把右边操作数的值赋给左边操作数
+=加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数C += A 相当于 C = C + A
-=减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数C -= A 相当于 C = C - A
*=乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数C *= A 相当于 C = C * A
/=除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数C /= A 相当于 C = C / A
%=求模且赋值运算符,求两个操作数的模赋值给左边操作数C %= A 相当于 C = C % A
<<=左移且赋值运算符C <<= 2 等同于 C = C << 2
>>=右移且赋值运算符C >>= 2 等同于 C = C >> 2
&=按位与且赋值运算符C &= 2 等同于 C = C & 2
^=按位异或且赋值运算符C ^= 2 等同于 C = C ^ 2
`=`按位或且赋值运算符

5.6 其他运算符

运算符描述实例
sizeof()返回数据类型的大小。sizeof(int),将返回 4.
typeof()返回 class 的类型。typeof(StreamReader);
&返回变量的地址。&a; 将得到变量的实际地址。
*变量的指针。*a; 将指向一个变量。
?:条件表达式如果条件为真 ? 则为 X : 否则为 Y
is判断对象是否为某一类型,返回值为bool类型If( Ford is Car) // 检查 Ford 是否是 Car 类的一个对象。
as强制转换,即使转换失败也不会抛出异常。Object obj = new StringReader(“Hello”); StringReader r = obj as StringReader;
x.m成员访问
x{…}数组和索引器访问
new T(…)对象和委托创建
new T(…){…}使用初始值设定项创建对象
new{…}匿名对象初始值设定
new T{…}数组创建
(T)x将x显式转换为T

5.7 运算符优先级

下表按运算符优先级从高到低列出各个运算符,在表达式中,较高优先级的运算符会优先被计算。

类别运算符结合性
后缀() 、[] 、-> 、.、 ++、 - -从左到右
一元+ 、-、 !、 ~、 ++、 - -、 (type)*、 &、 sizeof,一元表达式即其运算的变量只能有一个从右到左
乘除* 、/、 %从左到右
加减+ 、-从左到右
移位<< 、>>从左到右
关系< 、<=、 >、 >=从左到右
相等==、 !=从左到右
位与 AND&从左到右
位异或 XOR^从左到右
位或 OR``
逻辑与 AND&&从左到右
逻辑或 OR`
条件?:从右到左
赋值= 、+=、 -=、 *=、 /=、 %=、>>=、 <<=、 &= 、^=、 `=`
逗号,从左到右

6. 三大结构


与C语言的三大结构相同:顺序、选择、循环。顺序结构略讲。

6.1 条件结构

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

6.1.1 switch语句

语法:

switch(expression){
    case constant-expression  :
       statement(s);
       break; 
    case constant-expression  :
       statement(s);
       break; 
  
    /* 可以有任意数量的 case 语句 */
    default : /* 可选的 */
       statement(s);
       break; 
}

其中:

  • switch 语句中的expression 必须是一个整型或枚举类型或是一个 class 类型,其中 class 有一个单一的转换函数将其转换为整型或枚举类型。
  • case 的 constant-expression 必须与 switch 中的变量具有相同的数据类型,且必须是一个常量。
  • 当遇到 break 语句时,switch语句终止,控制流将跳转到 switch 语句后的下一行。
  • 不是每一个 case 都需要包含 break。如果 case 语句为空,则可以不包含 break,控制流将会继续后续的 case,直到遇到 break 为止。
  • 一个 switch 语句可以有一个可选的 default case,必须出现在 switch 的结尾。default case 可用于在上面所有 case 都不为真时执行的一个任务。default case 中的 break 语句不是必需的。

流程图:
在这里插入图片描述

6.1.2 条件运算符 ?:

条件运算符 ?:,可用来替代 if...else 语句。

Exp1 ? Exp2 : Exp3; // 其中Expx为表达式

该语句的结果取决于Exp1的值:

  • 如果 Exp1 为真,则计算 Exp2 的值,结果即为整个?:表达式的值。
  • 如果 Exp1 为假,则计算 Exp3 的值,结果即为整个 ?: 表达式的值。

6.2 循环结构

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

6.2.1 for/foreach 循环

// for语句语法例程
for (int a = 10; a < 20; a = a + 1)
{
	Console.WriteLine("a 的值: {0}", a);
}
int[] fibarray = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 };
foreach (int element in fibarray)
{
	System.Console.WriteLine(element);
}
	System.Console.WriteLine();

输出:
0
1
1
2
3
5
8
13

Foreach 语句官方文档
C# 中 foreach 遍历的用法

6.2.2 循环控制语句

控制语句描述
break 语句终止循环或 switch 语句,程序流继续执行紧接着循环或 switch 的下一条语句。即跳出当前循环语句
continue 语句跳出语句的本次循环

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/415404.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue 环境安装以及项目创建

环境安装 nodejs 安装 下载地址&#xff1a;https://nodejs.org/dist/v18.16.1/ 根据系统类型选择对应安装包&#xff0c;选择安装路径那个后一直下一步即可安装完成。 配置npm 代理镜像,设置为淘宝的镜像地址&#xff08;后面按照依赖可以加速下载安装包&#xff09; npm c…

Java介绍

计算机语言历史 1、软件的分类 软件从架构上分类&#xff1a; C/S(Client/Server)&#xff1a;基于客户端和服务器 B/S(Browser/Server)&#xff1a;基于浏览器和服务器 如何区分&#xff1a;如果使用时要安装则为C/S架构的&#xff0c;如果使用时用浏览器打开则为B/S架构 由于…

RDMA技术在Apache Spark中的应用

背景介绍 在当今数据驱动的时代&#xff0c;Apache Spark已经成为了处理大规模数据集的首选框架。作为一个开源的分布式计算系统&#xff0c;Spark因其高效的大数据处理能力而在各行各业中广受欢迎。无论是金融服务、电信、零售、医疗保健还是物联网&#xff0c;Spark的应用几…

共同学习|Spring Cloud Alibaba一一sentinel介绍

Sentinel介绍 介绍 alibaba/Sentinel Wiki GitHub 1、Sentinel是什么 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel以流量为切入点&#xff0c;从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 Sentinel 具有以下特征&a…

集合详解-迭代器遍历-增强for-List集合-List五种遍历方式-Set集合-排序规则Comparable-双列集合

Collection集合 数组和集合的区别 相同点 都是容器,可以存储多个数据 不同点 数组的长度是不可变的,集合的长度是可变的 数组可以存基本数据类型和引用数据类型 集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类 Collection 集合概述和使用 Collection…

安全评估与安全评价:区分核心概念

在当今信息化社会中&#xff0c;保护数据和网络安全变得尤为重要。为了确保系统和组织的安全&#xff0c;我们需要了解并正确运用安全评估和安全评价这两个核心概念。虽然它们听起来相似&#xff0c;但其实它们有着不同的定义和目的。 首先&#xff0c;安全评估是一种系统性的…

【Github】如何在Github上找到zotero插件的下载位置

最近博主在使用zotero时需要从github上下载一个插件&#xff0c;通过链接跳转到Github对应的用户下&#xff0c;可是还是花了一些时间才找到插件的具体位置&#xff0c;这里将我的经历分享给大家。 1、跳转到Github对应的用户下。 博主需要下载zotero中的中文文献识别插件Jas…

Adobe Acrobat DC中如何合并pdf并生成目录

一、利用 Acrobat 合成pdf目录 &#xff08;一&#xff09;新建标签&#xff08;更改标签等级等&#xff09; 1&#xff0c;用Adobe acrobat 软件打开待添加书签的pdf文档。 2&#xff0c;打开之后点击软件左边栏的书签&#xff08;有时被隐藏了&#xff0c;点击一下界面左边…

通过elementUI学习vue

<template><el-radio v-model"radio" label"1">备选项</el-radio><el-radio v-model"radio" label"2">备选项</el-radio> </template><script>export default {data () {return {radio: 1}…

Phoncent博客:探索AI写作与编程的无限可能

Phoncent博客&#xff0c;一个名为Phoncent的创新AIGC博客网站&#xff0c;于2023年诞生。它的创始人是庄泽峰&#xff0c;一个自媒体人和个人站长&#xff0c;他在网络营销推广领域有着丰富的经验。庄泽峰深知人工智能技术在内容创作和编程领域的潜力和创造力&#xff0c;因此…

【全志D1-H 哪吒开发板】Debian系统安装调教和点灯指南

全志D1-H开发板【哪吒】使用Deabian系统入门 特别说明&#xff1a; 因为涉及到操作较多&#xff0c;博文可能会导致格式丢失 其中内容&#xff0c;会根据后续使用做优化调整 目录&#xff1a; 参考资料固件烧录启动调教点灯问题 〇、参考资料 官方资料 开发板-D1开发板【…

面试笔记系列六之redis+kafka+zookeeper基础知识点整理及常见面试题

Redis redis持久化机制&#xff1a;RDB和AOF Redis 持久化 Redis 提供了不同级别的持久化方式: RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储. AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redi…

如何在aws服务器上部署mysql

在AWS服务器上部署 MySQL 数据库可以通过以下步骤完成&#xff1a; 启动 EC2 实例&#xff1a; 在 AWS 控制台中启动一个 EC2 实例&#xff0c;选择适合你需求的实例类型和配置。 安全组配置&#xff1a; 确保你的 EC2 实例的安全组配置允许来自你的 IP 地址的 MySQL 连接。默…

iOS中卡顿产生的主要原因及优化思路

卡顿本质上是一个UI体验上的问题&#xff0c;而UI的渲染及显示&#xff0c;主要涉及CPU和GPU两个层面。若 CPUGPU渲染耗时超过16.7ms&#xff0c;就会在屏幕vsync信号到来时无法更新屏幕内容&#xff0c;进而导致卡顿。 iOS中UI渲染主要包含Layout->Draw->Prepare->Co…

动态住宅IP vs 静态住宅IP,如何选择适合你的海外住宅IP?

随着数字时代的发展&#xff0c;网络已经成为了我们日常生活中不可或缺的一部分。在海外留学、旅游、工作或者进行电子商务等活动时&#xff0c;一个合适的住宅IP可以帮助我们保护个人隐私、确保网络连接的稳定性、提高在线服务的可靠性等。因此&#xff0c;选择适合自己的住宅…

“集世界精华·展中国风采”2024北京智能科技展会(世亚智博会)

在科技的浪潮中&#xff0c;中国犹如一艘乘风破浪的巨轮&#xff0c;稳健地航行在广阔的海洋上。随着科技的飞速发展&#xff0c;中国正逐渐成为全球科技领域的一面旗帜&#xff0c;引领着世界科技潮流。在这个伟大的时代&#xff0c;中国以卓越的科技创新能力和前瞻的战略视野…

JVM相关工具【jps、jstat、jinfo、jmap、jhat、jstack、VisualVM、GCEasy、MAT、GCViewer、Arthas】

JVM相关工具 JDK工具包jpsjstatjinfojmapjhatjstackVisualVM 第三方工具【GCEasy、MAT、GCViewer、Arthas】 转自 《极客时间》 JDK工具包 jps jstat jinfo jmap jhat jstack VisualVM 第三方工具【GCEasy、MAT、GCViewer、Arthas】

QT多语言切换功能

一.目的 在做项目时&#xff0c;有时希望我们的程序可以在不同的国家使用&#xff0c;这样最好的方式是一套程序能适应于多国语言。 Qt提供了这样的功能&#xff0c;使得一套程序可以呈现出不同的语言界面。本文将介绍QT如何实现多语言&#xff0c;以中文和英文为例。 QT开发…

数仓模型设计方法论

在当今大数据时代&#xff0c;数据已经成为企业最重要的资产之一。而数据仓库作为企业数据管理和分析的核心基础设施&#xff0c;其设计方法论对于企业的数据治理和决策分析至关重要。本文将探索数仓模型设计的方法论&#xff0c;帮助读者更好地理解和应用数仓模型设计。 一、…

2024最新Android面试题目,【设计思想解读开源框架】

前言 从18年毕业至今&#xff0c;就职过两家公司&#xff0c;大大小小项目做了几个&#xff0c;非常感谢我的两位老大&#xff0c;在我的android成长路上给予我很多指导&#xff0c;亦师亦友的关系。 从年前至今参加面试了很多公司&#xff0c;也收到了几家巨头的offer&#…
最新文章