1. 整数转换,整数和字符串,字符串和整数之间的转换怎么实现?
1.1整数到字符串的转换
-
使用
ToString()
方法整数类型(如
int
、long
等)都提供了ToString()
方法,可以直接将整数转换为字符串。int number = 123; string numberAsString = number.ToString(); Console.WriteLine(numberAsString); // 输出:123
-
拓:使用字符串插值或
$
前缀使用字符串插值或
$
前缀来简化字符串的构造,也可以用于将整数转换为字符串。int number = 123; string numberAsString = $"{number}"; Console.WriteLine(numberAsString); // 输出:123
1.2字符串到整数的转换
- 使用
int.Parse()
方法
int.Parse()
方法尝试将字符串参数转换为int
类型。如果字符串不是有效的整数表示,则会抛 出FormatException
异常;如果转换的数字超出了int
的范围,则会抛出OverflowException
。
string strNumber = "123";
int number = int.Parse(strNumber);
Console.WriteLine(number); // 输出:123
- 使用
int.TryParse()
方法
与int.Parse()
不同,int.TryParse()
方法尝试转换字符串,但不会抛出异常。如果转换成功, 它会返回true
,并将转换后的值存储在第二个参数(一个out
参数)中;如果转换失败,它会 返回false
,并且第二个参数不会被修改。
string strNumber = "123";
if (int.TryParse(strNumber, out int number))
{
Console.WriteLine(number); // 输出:123
}
else
{
Console.WriteLine("转换失败");
}
-
使用
Convert.ToInt32()
方法Convert.ToInt32()
方法也可以用于将字符串转换为int
类型。如果转换失败,它会抛出FormatException
或OverflowException
。string strNumber = "123"; int number = Convert.ToInt32(strNumber); Console.WriteLine(number); // 输出:123
对于其他整数类型(如long
、short
等),也有类似的Parse
、TryParse
和Convert
方法,可以根据需要选择使用。
2. 日期转换,获取当前日期,字符串转日期,日期转字符串怎么实现?
处理日期和时间通常涉及到DateTime
类,以及DateTime.Parse
、DateTime.TryParse
、ToString
等方法和格式化字符串。
2.1获取当前日期
使用DateTime.Now
或DateTime.UtcNow
来获取当前日期和时间。DateTime.Now
返回本地时间,而DateTime.UtcNow
返回协调世界时(UTC)。
DateTime now = DateTime.Now; // 获取当前本地日期和时间
DateTime utcNow = DateTime.UtcNow; // 获取当前UTC日期和时间
Console.WriteLine(now.ToString()); // 输出本地时间
Console.WriteLine(utcNow.ToString()); // 输出UTC时间
2.2字符串转日期
将字符串转换为DateTime
对象时,可以使用DateTime.Parse
或DateTime.TryParse
方法。Parse
方法在转换失败时会抛出异常,而TryParse
方法则不会,它会返回一个布尔值来表示转换是否成功。
string dateString = "2023-04-01";
// 使用DateTime.Parse(如果字符串格式不正确,将抛出异常)
DateTime date = DateTime.Parse(dateString);
// 使用DateTime.TryParse(更安全,不会抛出异常)
if (DateTime.TryParse(dateString, out DateTime parsedDate))
{
Console.WriteLine(parsedDate.ToString()); // 输出转换后的日期
}
else
{
Console.WriteLine("日期格式无效");
}
2.3日期转字符串
将DateTime
对象转换为字符串时,可以使用ToString
方法,并可以传递一个格式化字符串来指定输出格式。
DateTime date = DateTime.Now;
// 使用默认格式
string defaultString = date.ToString();
// 使用自定义格式
string customString = date.ToString("yyyy-MM-dd HH:mm:ss"); // 输出格式为年-月-日 时:分:秒
Console.WriteLine(defaultString);
Console.WriteLine(customString);
在ToString
方法的格式化字符串中,你可以使用各种自定义格式说明符来指定日期的各个部分如何显示。例如,yyyy
代表四位数的年份,MM
代表月份,dd
代表日,HH
代表小时(24小时制),mm
代表分,ss
代表秒。
注意:如果你知道字符串的确切格式,并且它可能与当前文化设置不匹配,你可以使用DateTime.ParseExact
或DateTime.TryParseExact
方法,并指定格式字符串和CultureInfo
对象。
3. 举例一维、二维、三维数组
数组是一种数据结构,用于在内存中连续存储相同类型的数据。
3.1一维数组
一维数组是最简单的数组形式,它代表了一个线性序列的数据。
// 初始化时直接赋值
int[] oneArray= { 1, 2, 3, 4, 5 };
// 访问数组元素
Console.WriteLine(oneArray[2]); // 输出:3
3.2 二维数组
二维数组是数组的数组,可以看作是表格或矩阵。每个元素都是一个一维数组。
int[,] twoDimensionalArray = new int[3, 4] {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
}; // 创建一个3行4列的二维数组, 初始化时直接赋值
// 访问数组元素
Console.WriteLine(twoDimensionalArray[1, 2]); // 输出:7
3.3三维数组
三维数组可以看作是数组的数组的数组,通常用于表示三维空间中的数据,如立方体中的值。
using System;
class Program
{
static void Main()
{
// 声明并初始化一个三维数组
int[,,] threeDimArray = new int[3, 3, 3]
{
{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
},
{
{10, 11, 12},
{13, 14, 15},
{16, 17, 18}
},
{
{19, 20, 21},
{22, 23, 24},
{25, 26, 27}
}
};
// 访问三维数组中的元素
// 例如,访问第一层的第二个元素(一个二维数组),然后从这个二维数组中访问第二行第三列的元素
// 这将是数字15
Console.WriteLine(threeDimArray[1, 1, 2]); // 输出: 15
// 遍历三维数组
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
for (int k = 0; k < 3; k++)
{
Console.Write(threeDimArray[i, j, k] + " ");
}
Console.WriteLine(); // 每完成一层二维数组的遍历后换行
if (j < 2) // 为了更好的格式,在两层二维数组之间添加两个空行
{
Console.WriteLine();
Console.WriteLine();
}
}
}
}
}
4. 需求:有个88笔费用记录,总额3亿,金额在300万~800万之间,随机数如何实现?并记录总耗时。
思路:
初始化:
初始化一个
Stopwatch
来记录操作所需的时间。定义常量,包括总交易数(
totalTransactions
)、目标总金额(totalAmountTarget
)、每笔交易的最小金额(minAmount
)和最大金额(maxAmount
)。创建一个
List<double>
来存储每笔交易的金额,并初始化一个变量sum
来累加这些交易的金额。初步分配:
计算每笔交易的平均金额(
average
),即目标总金额除以总交易数。通过循环,为每笔交易分配一个初始金额。这个初始金额是平均金额加上一个随机调整值(
adjustment
),该调整值在-10% * (maxAmount - minAmount)
到+10% * (maxAmount - minAmount)
之间随机选择,以确保每笔交易的金额都在最小值和最大值之间。将每笔交易的金额添加到
transactions
列表中,并累加到sum
变量中。检查并调整总额:
计算实际总金额(
sum
)与目标总金额(totalAmountTarget
)之间的差异(difference
)。如果这个差异的绝对值超过了目标总金额的5%,则需要进行调整。
计算需要调整的交易数(
adjustCount
),根据差异大小和每笔交易可调整的范围来计算。乘以了1.2来向上取整,确保有足够的空间进行调整。然后,将adjustCount
限制在总交易数以内。计算每笔需要调整的金额(
adjustAmountPerItem
),即差异除以需要调整的交易数。通过循环,将调整金额均分到最近的几笔交易中。如果差异为正,则增加这些交易的金额;如果差异为负,则减少这些交易的金额。同时,确保每笔交易的金额不会超出最小值和最大值的限制。
在循环中,还加入了一个条件检查,如果差异已经缩小到目标总金额的5%以内,则提前退出循环。
using System;
using System.Collections.Generic;
using System.Diagnostics;
class Program
{
static void Main(string[] args)
{
Stopwatch stopwatch = Stopwatch.StartNew();
const int totalTransactions = 88;
const double totalAmountTarget = 300000000; // 3亿
const double minAmount = 3000000; // 300万
const double maxAmount = 8000000; // 800万
List<double> transactions = new List<double>();
double sum = 0;
double average = totalAmountTarget / totalTransactions;
for (int i = 0; i < totalTransactions; i++)
{
double adjustment = (new Random().NextDouble() - 0.5) * (maxAmount - minAmount) * 0.1;
double amount = average + adjustment;
amount = Math.Max(amount, minAmount); // 确保不小于最小值
amount = Math.Min(amount, maxAmount);
transactions.Add(amount);
sum += amount;
}
double difference = totalAmountTarget - sum;
if (Math.Abs(difference) > totalAmountTarget * 0.05) // 误差超过5%
{
int adjustCount = (int)(Math.Abs(difference) / (maxAmount - minAmount) * 1.2); // 向上取整,确保有足够空间进行调整
adjustCount = Math.Min(adjustCount, totalTransactions); // 不超过总交易数
double adjustAmountPerItem = difference / adjustCount;
for (int i = 0; i < adjustCount && i < totalTransactions; i++)
{
if (difference > 0)
{
transactions[i] += adjustAmountPerItem;
transactions[i] = Math.Min(transactions[i], maxAmount); // 防止超出上限
}
else
{
transactions[i] -= adjustAmountPerItem;
transactions[i] = Math.Max(transactions[i], minAmount); // 防止低于下限
}
difference -= adjustAmountPerItem;
if (Math.Abs(difference) < totalAmountTarget * 0.005)
{
break;
}
}
}
Console.WriteLine("Total Transactions: " + totalTransactions);
Console.WriteLine("Total Amount: " + sum.ToString("N0"));
Console.WriteLine("Average Amount: " + (sum / totalTransactions).ToString("N0"));
Console.WriteLine("Elapsed Time: " + stopwatch.ElapsedMilliseconds + "ms");
foreach (var transaction in transactions)
{
Console.WriteLine(transaction.ToString("N0"));
}
}
}
5. 简述常见的集合类型的存储结构和它们的作用以及优缺点,并写出实现案例
集合类型是一种非常强大且常用的数据结构,用于存储和管理一系列的对象或值。
线性表
5.1 List<T>(泛型集合,可以被视为动态数组)
- 存储结构:基于数组的动态数组。
- 作用:存储和操作对象的序列,允许根据索引快速访问元素,支持动态扩容。
- 优点:随机访问速度快,支持动态添加和删除元素。
- 缺点:在列表中间插入或删除元素时性能较低,因为需要移动其他元素。
实现案例:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
numbers.Add(6); // 添加元素
numbers.Remove(3); // 删除元素
Console.WriteLine(numbers[2]); // 访问索引为2的元素
5.2 Array(数组)
- 存储结构:固定大小的连续内存块。
- 作用:存储固定数量的元素,类型相同。
- 优点:内存连续,访问速度快,固定大小可以提高内存效率。
- 缺点:大小不可变,灵活性差。
实现案例:
int[] numbers = { 1, 2, 3, 4, 5 };
Console.WriteLine(numbers[0]); // 访问索引为0的元素
5.3 LinkedList<T>(链表)
- 存储结构:双向链表。
- 作用:存储和操作对象的序列,支持在序列的任何位置快速添加和删除元素。
- 优点:在列表头部和尾部添加、删除元素时性能高。
- 缺点:随机访问性能低,需要遍历链表。
实现案例:
LinkedList<int> numbers = new LinkedList<int>();
numbers.AddLast(1);
numbers.AddFirst(0);
numbers.RemoveFirst();
foreach (var num in numbers)
{
Console.WriteLine(num);
}
5.4 Queue(队列,泛型集合)
队列是一种先进先出(FIFO, First-In-First-Out)的数据结构。它允许在队列的一端(队尾)添加元素,在另一端(队头)移除元素。
优点
- 先进先出:队列按照元素被添加的顺序进行处理,确保了元素的顺序性。
- 高效性:在大多数情况下,队列的插入和删除操作(在队尾插入和在队头删除)具有较高的效率。
- 适用性广:队列广泛应用于各种场景,如任务调度、消息传递、广度优先搜索等。
缺点
- 空间限制:基于数组实现的队列在扩容时可能会受到内存空间的限制。
- 并发性能:在并发环境下,普通的队列实现(如
Queue<T>
)可能不是线程安全的,需要额外的同步机制来保护数据一致性,这可能会影响性能。 - 访问特定元素:队列不支持通过索引直接访问特定位置的元素,这限制了其在某些场景下的使用。
C# 提供了 Queue<T>
泛型类来实现队列。
Queue<int> queue = new Queue<int>();
// 入队
queue.Enqueue(1);
queue.Enqueue(2);
// 出队
Console.WriteLine(queue.Dequeue()); // 输出 1
// 查看队首元素
Console.WriteLine(queue.Peek()); // 输出 2,但不移除
// 遍历队列
foreach (int item in queue)
{
Console.WriteLine(item);
}
Enqueue
方法是Queue<T>
类中的一个成员方法,用于在队列的末尾(队尾)插入一个新元素。
5.5 Stack(栈)
栈是一种后进先出(LIFO, Last-In-First-Out)的数据结构。它只允许在栈顶进行添加(push)或删除(pop)元素的操作。
Stack<int> stack = new Stack<int>();
// 入栈
stack.Push(1);
stack.Push(2);
// 出栈
Console.WriteLine(stack.Pop()); // 输出 2
// 查看栈顶元素
Console.WriteLine(stack.Peek()); // 输出 1,但不移除
// 遍历栈(注意:栈通常不推荐遍历,因为这会破坏LIFO的原则)
foreach (int item in stack.ToArray()) // 需要转换为数组或其他集合才能遍历
{
Console.WriteLine(item);
}
压栈(push,添加元素到栈顶)、弹栈(pop,移除栈顶元素并返回其值)、查看栈顶元素(peek或top,不移除栈顶元素但返回其值)等
哈希表
5.6 Hashtable<TKey, TValue>)(非泛型)
- 键值对存储:存储的元素是键值对。
- 非泛型集合:可以接受任何类型的键和值。
- 键的唯一性:每个键在Hashtable中都是唯一的。
- 无序:Hashtable中的元素是无序的,即它们不按照任何特定的顺序存储。
using System; using System.Collections; class Program { static void Main() { // 创建一个Hashtable实例 Hashtable hashtable = new Hashtable(); // 添加键值对 hashtable.Add("key1", "value1"); hashtable.Add("key2", 2); hashtable.Add("key3", true); // 访问Hashtable中的元素 Console.WriteLine("key1对应的值: " + hashtable["key1"]); Console.WriteLine("key2对应的值: " + hashtable["key2"]); Console.WriteLine("key3对应的值: " + hashtable["key3"]); // 检查键是否存在 if (hashtable.ContainsKey("key1")) { Console.WriteLine("key1存在于Hashtable中。"); } // 遍历Hashtable foreach (DictionaryEntry entry in hashtable) { Console.WriteLine("键: " + entry.Key + ", 值: " + entry.Value); } } }
在这个示例中,我们创建了一个
Hashtable
实例,并向其中添加了一些键值对。然后,我们通过键访问了这些值,并检查了某个键是否存在。最后,我们遍历了Hashtable
中的所有元素,并打印了它们的键和值。
5.7 HashSet<T>(泛型集合)
- 存储结构:哈希表。
- 作用:存储不重复的元素集合。
- 优点:添加、删除和查找元素的速度快,自动去重。
- 缺点:不支持索引访问,元素顺序不固定。
实现案例
HashSet<int> numbers = new HashSet<int> { 1, 2, 3, 4, 5, 5 }; // 自动去重
numbers.Add(6);
numbers.Remove(3);
foreach (var num in numbers)
{
Console.WriteLine(num);
}
概念:
HashSet<T>
是一个泛型集合,它存储不重复的元素。HashSet 使用哈希表来存储元素,因此它可以提供非常快速的元素查找、插入和删除操作。用途:
HashSet<T>
常用于需要快速检查一个元素是否存在于集合中,或者需要从集合中移除重复元素的场景。优点:
- 快速查找:由于使用了哈希表,HashSet 提供了平均时间复杂度为O(1)的查找、插入和删除操作。
- 不重复元素:HashSet 自动确保集合中不包含重复的元素。
- 灵活性:可以存储任何类型的对象,只要它们支持相等性比较(
IEquatable<T>
或object.Equals
)。缺点:
- 无序:HashSet 不保证元素的顺序,特别是当元素被添加或删除时。
- 不支持索引访问:由于HashSet是一个集合,它不支持通过索引来访问元素。
- 内存占用:与字典类似,HashSet 由于需要存储元素以及它们的哈希码和内部状态信息,可能会占用比数组或列表更多的内存。