目录
定义方式:结构体使用 struct 关键字进行定义。结构体通常用于表示小型、轻量级的数据类型
值类型:结构体是值类型,这意味着它们在赋值或传递时会被复制。每个结构体的实例都具有独立的值
一、常量
概念:常量(const
)是指一个在编译时就确定其值且在程序运行期间不会改变的变量(比如银行系统)
声明方式:
const 变量类型 变量名 = 值
const int MaxValue = 100; const string WelcomeMessage = "Hello, World!"; //MaxValue = 100 ;不能给常量重新赋值
二、枚举
概念:枚举(enum
)是一种特殊的值类型,用于定义一组具有名称的常量。这些常量通常代表一个有意义的整数值
定义方式:
枚举使用
enum
关键字进行定义。每个枚举成员都有一个名称和对应的整数值。[ ]:表示可写可不写
public:访问修饰符
[public] enum 枚举名 { 值1, // 默认值为0 值2, // 默认值为1 ......... 值3 // 默认值为2 }
public enum Colors
{
Red,
Green,
Blue
}
public class Program
{
public static void Main()
{
Colors myColor = Colors.Green;
switch (myColor)
{
case Colors.Red:
Console.WriteLine("The color is Red.");
break;
case Colors.Green:
Console.WriteLine("The color is Green.");
break;
case Colors.Blue:
Console.WriteLine("The color is Blue.");
break;
}
}
}
1、枚举类型和int以及string类型之间的转换
/*将 int 转换为枚举类型:
使用显式类型转换(强制转换)可以将整数值转换为对应的枚举值:*/
public enum DaysOfWeek
{
Sunday = 1,
Monday = 2,
Tuesday = 3,
Wednesday = 4,
Thursday = 5,
Friday = 6,
Saturday = 7
}
public class Program
{
public static void Main()
{
int dayNumber = 5;
DaysOfWeek day = (DaysOfWeek)dayNumber;
Console.WriteLine(day); // 输出:Thursday
}
}
/*将枚举类型转换为 int:
可以直接将枚举值转换为 int 类型*/
public class Program
{
public static void Main()
{
DaysOfWeek day = DaysOfWeek.Friday;
int dayNumber = (int)day;
Console.WriteLine(dayNumber); // 输出:6
}
}
/*将枚举转换为 string:
使用 ToString() 方法可以将枚举值转换为其名称的字符串*/
public class Program
{
public static void Main()
{
DaysOfWeek day = DaysOfWeek.Monday;
string dayName = day.ToString();
Console.WriteLine(dayName); // 输出:Monday
}
}
public class Program
{
public static void Main()
{
string dayName = "Wednesday";
// 使用 Enum.Parse()
DaysOfWeek day = (DaysOfWeek)Enum.Parse(typeof(DaysOfWeek), dayName);
Console.WriteLine(day); // 输出:Wednesday
// 使用 Enum.TryParse()
if (Enum.TryParse(dayName, out DaysOfWeek parsedDay))
{
Console.WriteLine(parsedDay); // 输出:Wednesday
}
else
{
Console.WriteLine("Invalid day name.");
}
}
}
2、注意事项
-
强制转换(Explicit Casting):
- 在将
int
转换为枚举时,如果int
的值不在枚举定义的范围内,强制转换不会抛出异常,但会得到不符合预期的枚举值。 - 在将枚举转换为
int
时,得到的是枚举成员定义的整数值。
- 在将
-
Enum.Parse
和Enum.TryParse
:Enum.Parse
会抛出ArgumentException
如果字符串值不匹配任何枚举成员。Enum.TryParse
不会抛出异常,它通过返回值来指示转换是否成功,并通过out
参数返回解析的枚举值。
三、结构
概念:结构体(struct
)是一种值类型的数据结构,用于封装一组相关的数据。结构体与类(class
)类似,但它们在内存管理、性能和使用场景上有一些显著的不同点。
-
定义方式:结构体使用
struct
关键字进行定义。结构体通常用于表示小型、轻量级的数据类型
public struct Point
{
public int X;
public int Y; //字段:存储数据,程序运行时可以存储一个值,而字段可以存多个值
public Point(int x, int y) //构造函数
{
X = x;
Y = y;
}
public override string ToString() //方法
{
return $"({X}, {Y})";
}
}
-
值类型:结构体是值类型,这意味着它们在赋值或传递时会被复制。每个结构体的实例都具有独立的值
public class Program
{
public static void Main()
{
Point p1 = new Point(1, 2);
Point p2 = p1; // 复制 p1 的值
p2.X = 10;
Console.WriteLine(p1); // 输出:(1, 2)
Console.WriteLine(p2); // 输出:(10, 2)
}
}
- 内存管理:结构体存储在栈上(当作为局部变量时)或嵌入在其他对象中(当作为字段时),这使得2、它们通常比类对象(存储在堆上)具有更高的性能。由于结构体是值类型,它们不具有继承的特性,也不能使用类的特性(如虚方法)
- 构造函数:结构体可以有构造函数,但必须为所有字段提供初始化值。结构体不能有无参数构造函数,编译器自动提供一个默认的无参数构造函数,所有字段都会初始化为其默认值。
- 方法和属性:结构体可以包含方法、属性、字段、事件和构造函数。它们也可以实现接口,但不能继承自其他结构体或类
-
比较和相等:由于结构体是值类型,它们的比较是基于值的。
Equals()
方法和==
运算符会比较结构体的所有字段值是否相等。
public class Program
{
public static void Main()
{
Rectangle r1 = new Rectangle(4, 5);
Rectangle r2 = new Rectangle(4, 5);
Console.WriteLine(r1.Equals(r2)); // 输出:True
Console.WriteLine(r1 == r2); // 输出:True
}
}
示例代码:
public struct Circle
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
public double Area()
{
return Math.PI * Radius * Radius;
}
public override string ToString()
{
return $"Circle with Radius: {Radius}";
}
}
public class Program
{
public static void Main()
{
Circle c = new Circle(5.0);
Console.WriteLine(c); // 输出:Circle with Radius: 5
Console.WriteLine($"Area: {c.Area()}"); // 输出:Area: 78.53981633974483
}
}
四、一维数组
概念:数组是一种数据结构,用于存储固定大小的、相同类型的元素的集合。它允许你通过索引来访问和操作这些元素。数组的大小在创建时确定,并且不能在运行时动态调整。
1、语法:
数组类型[ ] 数组名 = new 数组类型[数组长度](数组的长度一旦固定了,就不能再被改变)
using System;
class Program
{
static void Main()
//数组的定义方法
{
int[] numbers = new int[5];
int[] numbers1 = new int[] { 1, 2, 3, 4, 5 };
int[] numbers2 = { 1, 2, 3, 4, 5 };
int[] numbers3 =new int[3] { 1, 2, 3};
//数组的访问
int firstNumber = numbers[0]; // 访问第一个元素,值为 1
numbers[2] = 10; // 修改第三个元素的值为 10
// 使用 for 循环 遍历数组
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine(numbers[i]);
}
Console.ReadKey();
}
}
五、方法(函数)
概念:函数就是将一堆代码进行重用的一种机制
1、语法:
[public] static 返回值类型 方法名([参数列表])
{
方法体;
}调用方法:类名.方法名(参数);(如果在同一个类中可省略类名)
public class Program
{
// 定义一个无返回值的方法,接受两个整数参数并打印它们的和
public static void PrintSum(int a, int b)
{
int sum = a + b;
Console.WriteLine("Sum: " + sum);
}
public static void Main()
{
// 调用 PrintSum 方法,传递两个整数参数
PrintSum(5, 10); // 输出:Sum: 15
}
}
六、out参数
1、概念
out
关键字用于将参数按引用传递到方法中,允许方法初始化并返回一个新的值给调用者。与 ref
参数不同,out
参数在传递给方法之前不需要初始化。out
参数主要用于在方法中返回多个值或者处理某些需要修改的情况。
2、定义
- 定义:在方法的参数列表中使用
out
关键字,表示该参数是按引用传递的,并且在方法内部必须被初始化。 - 调用:调用方法时,需要使用
out
关键字来标识该参数。 - 初始化:传递给
out
参数的变量不需要在调用前初始化,因为方法内部会给它赋值。
/*TryParse 方法尝试将字符串转换为整数。如果转换成功,返回 true 并将转换结果赋值给 out 参数 number;否则返回 false。
在 Main 方法中,result 变量被声明但未初始化,随后被 TryParse 方法初始化并赋值。如果转换成功,result 将包含转换后的整数值。*/
using System;
class Program
{
static void Main()
{
int result;
bool success = TryParse("123", out result);
if (success)
{
Console.WriteLine($"Parsed value: {result}");
}
else
{
Console.WriteLine("Failed to parse the input.");
}
}
static bool TryParse(string input, out int number)
{
return int.TryParse(input, out number);
}
}
/*CalculateValues 方法计算两个整数的和与积,并通过 out 参数返回结果。
在 Main 方法中,调用 CalculateValues 时,sum 和 product 变量用于接收计算结果,CalculateValues 方法对它们进行初始化。*/
using System;
class Program
{
static void Main()
{
int sum, product;
CalculateValues(5, 3, out sum, out product);
Console.WriteLine($"Sum: {sum}, Product: {product}");
}
static void CalculateValues(int a, int b, out int sum, out int product)
{
sum = a + b;
product = a * b;
}
}
/*CalculateDifferenceAndSum 方法计算两个整数的差值和和,并通过 ref 和 out 参数返回结果。
difference 使用 ref 传递,表示输入值减去当前值,sum 使用 out 传递,表示输入值与当前值的和。在方法内部进行初始化并返回。*/
using System;
class Program
{
static void Main()
{
int value1 = 10;
int value2;
bool result = CalculateDifferenceAndSum(15, ref value1, out value2);
Console.WriteLine($"Difference: {value1}, Sum: {value2}");
Console.WriteLine($"Calculation successful: {result}");
}
static bool CalculateDifferenceAndSum(int input, ref int difference, out int sum)
{
sum = input + difference;
difference = input - difference;
return true;
}
}
3、注意事项
- 初始化:
out
参数在方法内部必须被初始化,否则编译器会报错。调用方法前不需要初始化。 - 方法定义和调用一致:在方法定义和调用中,
out
关键字必须都出现。 - 避免混淆:虽然
out
和ref
都是按引用传递参数,但ref
参数需要在传递前初始化,而out
参数在传递前不需要初始化,只需要在方法内部被赋值。
七、ref参数
1、概念:
ref
关键字用于将参数按引用传递给方法,这样方法内部对参数的修改会反映到调用者传递的变量上。使用 ref
参数可以让方法修改传入的变量的值,而不仅仅是读取它。
2、ref
参数的定义
- 定义:在方法的参数列表中使用
ref
关键字,指明该参数是按引用传递的。 - 调用:在调用方法时,也需要使用
ref
关键字来标识该参数。 - 初始化:传递给
ref
参数的变量在使用之前必须初始化,因为方法内部可能会修改它的值。
/*number 变量在 Main 方法中被初始化为 10。
在调用 ModifyValue 方法时,number 被传递为 ref 参数,因此方法内部的修改(value += 10)会影响到 number。
方法执行后,number 的值变为 20。*/
using System;
class Program
{
static void Main()
{
int number = 10;
Console.WriteLine($"Before: {number}");
ModifyValue(ref number);
Console.WriteLine($"After: {number}");
}
static void ModifyValue(ref int value)
{
value += 10;
}
}
/*a 和 b 在 Main 方法中被初始化。
调用 Swap 方法时,将 a 和 b 作为 ref 参数传递。这样,Swap 方法可以直接修改这两个变量的值。
Swap 方法交换了 a 和 b 的值,最终输出 a = 10 和 b = 5*/
using System;
class Program
{
static void Main()
{
int a = 5;
int b = 10;
Console.WriteLine($"Before: a = {a}, b = {b}");
Swap(ref a, ref b);
Console.WriteLine($"After: a = {a}, b = {b}");
}
static void Swap(ref int x, ref int y)
{
int temp = x;
x = y;
y = temp;
}
}
3、注意事项
- 初始化:在将变量作为
ref
参数传递之前,必须对其进行初始化。 - 调用和定义一致:在方法调用和方法定义中,都必须使用
ref
关键字。 - 方法内的修改:
ref
参数的修改在方法外部是可见的,因此需要谨慎使用,以避免意外修改数据。
八、params可变参数
1、概念:
params
关键字用于允许一个方法接受可变数量的参数。使用 params
关键字时,方法可以接收零个或多个参数,并将它们作为一个数组处理。
2、params
参数的定义
- 位置:
params
参数必须是方法参数列表中的最后一个参数。 - 类型:
params
参数的类型必须是数组类型。例如,你可以使用int[]
、string[]
等类型。 - 传递:当调用使用
params
关键字定义的方法时,可以传递多个值,也可以传递一个数组或不传递任何值。
/*PrintNumbers 方法定义了一个 params 参数 numbers,它可以接收任意数量的 int 参数。
在 Main 方法中,PrintNumbers 被调用了三次:一次传递多个参数,一次传递一个数组,一次不传递任何参数。
无论传递了多少参数,PrintNumbers 方法都会正确地打印这些参数。*/
using System;
class Program
{
static void Main()
{
// 调用方法时传递多个参数
PrintNumbers(1, 2, 3, 4, 5);
// 调用方法时传递一个数组
int[] numbers = { 10, 20, 30 };
PrintNumbers(numbers);
// 调用方法时不传递任何参数
PrintNumbers();
}
static void PrintNumbers(params int[] numbers)
{
foreach (int number in numbers)
{
Console.Write(number + " ");
}
Console.WriteLine();
}
}
/*ConcatenateAndPrint 方法接收一个字符串 prefix 和一个 params 参数 numbers。
调用 ConcatenateAndPrint 时,字符串 prefix 是第一个参数,后面的整数值被作为数组传递给 numbers 参数。
方法输出了前缀和所有整数值。*/
using System;
class Program
{
static void Main()
{
// 传递一个额外的字符串参数和多个整数参数
ConcatenateAndPrint("Numbers:", 1, 2, 3, 4, 5);
}
static void ConcatenateAndPrint(string prefix, params int[] numbers)
{
Console.Write(prefix + " ");
foreach (int number in numbers)
{
Console.Write(number + " ");
}
Console.WriteLine();
}
}
3、注意事项:
- 只能有一个
params
参数:一个方法中只能有一个params
参数,并且它必须是参数列表中的最后一个。 - 可以传递数组:你可以直接传递一个数组给
params
参数,这会将数组作为参数传递到方法中。 - 类型一致性:
params
参数的类型必须与方法定义中的数组类型一致。
九、方法的重载
1、概念
方法重载(Method Overloading) 是一种允许在同一个类中定义多个方法,这些方法具有相同的名称但参数列表不同的技术。方法重载可以使代码更具可读性和可维护性,因为它允许使用相同的方法名称来执行类似但参数不同的操作。
2、定义
- 定义:方法重载是在同一个类中定义多个具有相同名称但参数列表(参数的类型、数量或顺序)不同的方法。这些方法具有不同的签名。
- 签名:方法的签名由方法名称和参数列表组成。返回类型不作为重载的区分标准。
- 调用:根据传递给方法的参数类型和数量,编译器会选择匹配的方法进行调用。
/*PrintMessage 方法被重载两次,一次接受一个字符串参数,另一次接受两个字符串参数。
根据传递的参数数量,编译器会选择合适的重载方法进行调用。在 Main 方法中,调用 PrintMessage("Hello") 使用的是第一个重载,而调用 PrintMessage("Hello", "World") 使用的是第二个重载。*/
using System;
class Program
{
static void Main()
{
PrintMessage("Hello");
PrintMessage("Hello", "World");
}
static void PrintMessage(string message)
{
Console.WriteLine(message);
}
static void PrintMessage(string message1, string message2)
{
Console.WriteLine($"{message1} {message2}");
}
}
/*Add 方法被重载三次,分别处理整数、浮点数和字符串的加法。
根据传递的参数类型,编译器会选择匹配的重载方法。在 Main 方法中,Add(5, 10) 调用整型重载,Add(3.5, 2.5) 调用浮点数重载,而 Add("Hello ", "World") 调用字符串重载。*/
using System;
class Program
{
static void Main()
{
Console.WriteLine(Add(5, 10)); // 整数相加
Console.WriteLine(Add(3.5, 2.5)); // 浮点数相加
Console.WriteLine(Add("Hello ", "World")); // 字符串拼接
}
static int Add(int a, int b)
{
return a + b;
}
static double Add(double a, double b)
{
return a + b;
}
static string Add(string a, string b)
{
return a + b;
}
}
/*Display 方法被重载两次,一次参数顺序为 string 和 int,另一次为 int 和 string。
根据参数的顺序,编译器会选择合适的重载方法。在 Main 方法中,Display("Hello", 10) 调用第一个重载,而 Display(10, "Hello") 调用第二个重载。*/
using System;
class Program
{
static void Main()
{
Display("Hello", 10);
Display(10, "Hello");
}
static void Display(string text, int number)
{
Console.WriteLine($"Text: {text}, Number: {number}");
}
static void Display(int number, string text)
{
Console.WriteLine($"Number: {number}, Text: {text}");
}
}
3、注意事项
- 参数列表必须不同:要重载方法,必须确保方法的参数列表(包括参数类型、数量或顺序)不同。如果仅更改返回类型而参数列表相同,编译器会报错。
- 可读性:过多的重载可能会导致代码混乱,影响可读性。合理使用重载能够提升代码的清晰度和简洁性。
- 编译器选择:编译器会根据方法的签名和传递的参数类型选择最匹配的方法。如果存在多个匹配的方法,可能会导致编译错误或无法确定调用的方法。
十、方法的递归
1、概念
方法递归(Recursion)是指在一个方法的定义中直接或间接调用该方法自身的编程技巧。递归可以使解决某些问题变得更加简洁和直观,特别是当问题可以分解为相似的子问题时。使用递归时,通常需要定义一个或多个基准条件(base cases)来终止递归,以防止无限循环。
2、定义
- 递归方法:一个方法在其执行过程中调用自身。
- 基准条件:递归的终止条件,防止递归无限进行。每个递归方法必须有至少一个基准条件,以确保递归最终会停止。
- 递归条件:方法在执行时调用自身的条件,通常会对问题进行简化或分解。
/*Factorial 方法计算一个整数的阶乘。
基准条件是 n == 0 时返回 1。
递归条件是 n * Factorial(n - 1),即将问题分解为更小的子问题。*/
using System;
class Program
{
static void Main()
{
int number = 5;
Console.WriteLine($"{number}! = {Factorial(number)}");
}
static int Factorial(int n)
{
// 基准条件:如果 n 为 0,则返回 1
if (n == 0)
{
return 1;
}
// 递归条件:n! = n * (n-1)!
else
{
return n * Factorial(n - 1);
}
}
}
/*Fibonacci 方法计算第 n 个斐波那契数。
基准条件是 n == 0 和 n == 1 时返回 0 和 1。
递归条件是 Fibonacci(n - 1) + Fibonacci(n - 2),即将问题分解为更小的子问题。*/
using System;
class Program
{
static void Main()
{
int n = 10;
Console.WriteLine($"Fibonacci({n}) = {Fibonacci(n)}");
}
static int Fibonacci(int n)
{
// 基准条件:F(0) = 0 和 F(1) = 1
if (n == 0)
{
return 0;
}
else if (n == 1)
{
return 1;
}
// 递归条件:F(n) = F(n-1) + F(n-2)
else
{
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
}
}
/*Reverse 方法反转给定的字符串。
基准条件是字符串长度小于等于 1 时直接返回字符串。
递归条件是 Reverse(str.Substring(1)) + str[0],即对字符串的子串递归反转,并将当前字符添加到末尾。*/
using System;
class Program
{
static void Main()
{
string str = "hello";
Console.WriteLine($"Reversed string: {Reverse(str)}");
}
static string Reverse(string str)
{
// 基准条件:空字符串或只有一个字符
if (str.Length <= 1)
{
return str;
}
// 递归条件:反转字符串的递归部分 + 当前字符
else
{
return Reverse(str.Substring(1)) + str[0];
}
}
}
3、注意事项
- 基准条件:递归方法必须包含基准条件,确保递归能够停止。否则,可能导致无限递归和栈溢出错误。
- 栈空间:递归调用会消耗栈空间,因此在处理深度递归时要注意性能和内存消耗。对于很深的递归,可能会导致栈溢出。
- 性能:某些递归算法的性能可能不如迭代方法,特别是对于较大规模的问题。考虑使用动态规划或其他优化技术来提高性能。