C#学习目录
基础学习
基础知识
using关键字
using
关键字用于在程序中包含命名空间。
在C#程序中 第一条语句都是:
using System;
class关键字
class
关键字用于声明一个类
C#中的注释
注释是用于解释代码。编译器会忽略注释的条目。
// 这是单行注释
/*
这是
多行
注释
*/
标识符
标识符是用来识别类、变量、函数或任何其它用户定义的项目。在 C# 中,类的命名必须遵循如下基本规则:
- 标识符必须以字母、下划线或 @开头,后面可以跟一系列的字母、数字( 0 - 9 )、下划线( _ )、@。
tips: C/C++中不允许@ - 标识符中的第一个字符不能是数字。
- 标识符必须不包含任何嵌入的空格或符号,比如 ? - +! # % ^ & * ( ) [ ] { } . ; : " ’ / \。
- 标识符不能是 C# 关键字。除非它们有一个 @ 前缀。 例如,@if 是有效的标识符,但 if 不是,因为 if 是关键字。
- 标识符必须区分大小写。大写字母和小写字母被认为是不同的字母。
- 不能与C#的类库名称相同。
数据类型
在 C# 中,变量分为以下几种类型:
- 值类型(Value types)
- 引用类型(Reference types)
- 指针类型(Pointer types)
值类型
值类型变量可以直接分配给一个值。它们是从类 System.ValueType 中派生的。
表达式 sizeof(type) 产生以字节为单位存储对象或类型的存储尺寸
部分值类型(C/C++没有的):
byte
8位无符号整数
sbyte
8位有符号整数
decimal
128位精确的十进制值,28-29 有效位数 -79,228,162,514,264,337,593,543,950,335 到+79,228,162,514,264,337,593,543,950,335 (要在一个浮点类型的值后加一个大写或小写的M)
引用类型(Reference types)
引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。换句话说,它们指的是一个内存位置。使用多个变量的引用指向一个同内存位置,如果内存位置的数据是由其中一个变量改变的,其他变量会自动反映这种值的变化。内置的引用类型有:object、dynamic 和 string。
对象(object)类型
对象(Object)类型是 C# 通用类型系统(Common Type System - CTS)中所有数据类型的终极基类。Object 是 System.Object 类的别名。所以Object类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。
当一个值类型转换为对象类型时,则被称为装箱;另一方面,当一个对象类型转换为值类型时,则被称为拆箱。
object obj;
obj = 100; // 这是装箱
动态(dynamic)类型
您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。
声明动态类型的语法:dynamic <variable_name> = value;
dynamic d = 10;
动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的
字符串(string)类型
字符串(String)类型允许您给变量分配任何字符串值。字符串(String)类型是 System.String 类的别名。它是从对象(Object)类型派生的。字符串(String)类型的值可以通过两种形式进行分配:引号和 @引号。
C# string 字符串的前面可以加 @(称作"逐字字符串")将转义字符(\)当作普通字符对待
比如:string str = @"C:\Windows";
等价于:string str = "C:\\Windows";
另外@ 字符串中可以任意换行,换行符及缩进空格都计算在字符串长度之内。
指针类型(Pointer types)
指针类型变量存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。
声明指针类型的语法:type* identifier;
char* cptr;
int* iptr;
类型转换
C# 中的类型转换可以分为两种:隐式类型转换和显式类型转换(也称为强制类型转换)。
隐式类型转换
从小的整数类型转换为大的整数类型,从派生类转换为基类。
int intValue = 10;
long longValue = intValue; // 编译器会自动转换
显示转换
将一个较大范围的数据类型转换为较小范围的数据类型时,或者将一个对象类型转换为另一个对象类型。强制转换会造成数据丢失。
double doubleValue = 3.14;
int intValue = (int)doubleValue; // (type)显式强转 数据可能会损失
string stringValue = intValue.ToString(); // 转换为字符串
C#类型转换方法
C# 提供了下列内置的类型转换方法:
变量
一个变量只不过是一个供程序操作的存储区的名字。每个变量都有一个特定的类型,类型决定了变量的内存大小和布局。
变量定义及初始化
// <data_type> <variable_list>;
// <data_type> <variable_name> = value;
// 例如:
int i, j, k;
char c, ch;
c = 'c';
byte z = 22;
接受来自用户的值
System 命名空间中的 Console 类提供了一个函数 ReadLine()
,用于接收来自用户的输入,并把它存储到一个变量中。
int num;
num = Convert.ToInt32(Console.ReadLine());
// Console.ReadLine() 只接受字符串格式的数据 所以用Convert.ToInt32() 转换成int
C# 中的 Lvalues 和 Rvalues
C# 中的两种表达式:
-
lvalue:lvalue 表达式可以出现在赋值语句的左边或右边。
-
rvalue:rvalue 表达式可以出现在赋值语句的右边,不能出现在赋值语句的左边。
变量是 lvalue 的,所以可以出现在赋值语句的左边。数值是 rvalue 的,因此不能被赋值,不能出现在赋值语句的左边。
int g = 20; // 有效
10 = 20; // 无效 编译错误
常量
基本同C/C++,定义使用const关键字
整数常量
前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,没有前缀则表示十进制。
字符串常量
字符串常量是括在双引号 ""
里,或者是括在 @""
里,其中 @""
一比一等于你给变量的值 包括换行和空格数
运算符
大部分同C/C++。 部分不同如下:
运算符优先级
判断
switch
case
的:
后面若不为空必须跟break;
否则报错,若为空,会一直往下到第一个不为空的地方进行相应操作
循环
for (; ; ) { } // 无限循环
for (int i = 0; i < 5; i++)
{
Console.Write(i); // 01234
a[i] = i << 1;
}
Console.WriteLine();
foreach (int i in a) // 类似 C++: for (int i : a) { }
{
Console.Write(i); // 02468
}
封装
封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。
封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。
一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:
public:所有对象都可以访问。
private:只有同一个类中的函数在对象内部可以访问它的私有成员。即使是类的实例也不能访问它的私有成员。
protected:只有该类对象及其子类对象可以访问
internal:带有 internal 访问修饰符的任何成员可以被定义在该成员所定义的应用程序内的任何类或方法访问。
protected internal:访问限于当前程序集或派生自包含类的类型。访问修饰符允许在本类,派生类或者包含该类的程序集中访问。
方法
定义以及调用
当定义一个方法时,从根本上说是在声明他的结构的元素。在C#中,定义方法的语法如下:
<Access Specifier> <Return Type> <Method Name>(Parameter List) // 访问修饰符 返回类型 方法名称 参数列表
{
Method Body // 方法主体
}
using System;
class Number
{
public int FindMax(int num1, int num2)
{
return num1 > num2 ? num1 : num2;
}
static void Main(string[] args)
{
int a = 1000;
int b = 2000;
Number num = new Number();
Console.WriteLine("Max is: " + num.FindMax(a, b)); // Max is: 2000
}
}
参数传递
按值传递:基本与C++无异
引用传递:这种方式复制参数的内存位置的引用给形式参数。这意味着,当形参的值发生改变时,同时也改变实参的值。
输出参数:这种方式可以返回多个值
按引用传递参数
是一个对变量的内存位置的引用。它不会为这些参数创建一个新的存储位置。引用参数表示与提供给方法的实际参数具有相同的内存位置。
class CSharpTest
{
public void change(ref int a, ref int b)
{
a *= b;
}
static void Main()
{
CSharpTest t = new CSharpTest();
int a = 5, b = 10;
Console.WriteLine(a + " " + b); // 5 10
t.change(ref a, ref b); // 传参时需要标明
Console.WriteLine(a + " " + b); // 5 10
}
}
桉输出传递参数
return 语句可用于只从函数中返回一个值。但是,可以使用输出参数来从函数中返回更多的值。输出参数会把方法输出的数据赋给自己,其他方面与引用参数相似。
class CSharpTest
{
int a = 5;
public int Test(out int x)
{
int temp = 5;
x = temp * temp;
Console.WriteLine("before function end: " + a); // 25
return temp;
}
static void Main(string[] args)
{
CSharpTest test = new CSharpTest();
Console.WriteLine("before function start: " + test.a); // 5
int t = test.Test(out test.a); // 传参时需要标明
Console.WriteLine("common return: " + t + " out parameter: " + test.a); // 5 25
}
}
可空类型
C#提供了一个特俗的数据类型——nullable类型(可空类型),可空类型可以表示其基础值类型正常范围内的值,再加上一个null值。例如,Nullable<Int32>,读作“可空的Int32”,可以被赋值为 -2,147,483,648 到 2,147,483,647 之间的任意值,也可以被赋值为 null 值。
在处理数据库和其他包含可能未赋值的元素的数据类型时,将 null 赋值给数值类型或布尔型的功能特别有用。例如,数据库中的布尔型字段可以存储值 true 或 false,或者,该字段也可以未定义。
空类型啥也没有:
int? a = new int?(); // 空
int? temp = new int(); // 0
Null 合并运算符( ?? )
如果第一个操作数的值为null,则运算符返回第二个操作数的值,否则返回第一个操作数的值。
int? a = new int?();
int? b = new int?(1);
int? c = new int?(2);
Console.WriteLine(a ?? b); // 1
Console.WriteLine(c ?? b); // 2
数组
声明数组
datatype[] arrayName; // 格式
int[] a;
其中:
- datatype用于指定被存储在数组中的元素的类型
- [ ]指定数组的秩(维度),秩指定数组的大小
- arrayName指定数组的名称。
初始化数组
实践探索
自定义容器模板
emmm 本人学到一半 发现用不着了 然后就没再写了 所以以下内容不一定完整 佬们看个乐就行
namespace MyContainer
{
#region Pair // csharp内置的KeyValuePair<K, V> 对于K和V 只有get 没有set
public class Pair<TKey, TValue> : IComparable<Pair<TKey, TValue>> where TKey : IComparable<TKey> where TValue : IComparable<TValue>
{
public TKey? key { get; set; }
public TValue? value { get; set; }
public Pair() { }
public Pair(TKey key, TValue value)
{
this.key = key;
this.value = value;
}
/// <summary>
/// 贴合cpp习惯 加入first second的使用
/// </summary>
public TKey first
{
get => key;
set => key = value;
}
public TValue second
{
get => this.value;
set => this.value = value;
}
/// <summary>
/// 重载运算符
/// </summary>
public static bool operator ==(Pair<TKey, TValue> lhs, Pair<TKey, TValue> rhs)
{
return (lhs.first.CompareTo(rhs.first) == 0 && lhs.second.CompareTo(rhs.second) == 0);
}
public static bool operator !=(Pair<TKey, TValue> lhs, Pair<TKey, TValue> rhs)
{
return !(lhs == rhs);
}
public static bool operator <(Pair<TKey, TValue> lhs, Pair<TKey, TValue> rhs)
{
return (lhs.first.CompareTo(rhs.first) < 0 || lhs.first.CompareTo(rhs.first) == 0 && lhs.second.CompareTo(rhs.second) < 0);
}
public static bool operator >(Pair<TKey, TValue> lhs, Pair<TKey, TValue> rhs)
{
return (lhs.first.CompareTo(rhs.first) > 0 || lhs.first.CompareTo(rhs.first) == 0 && lhs.second.CompareTo(rhs.second) > 0);
}
public int CompareTo(Pair<TKey, TValue>? other)
{
if (this == other) return 0;
if (this < other) return -1;
return 1;
}
}
#endregion
#region MyQueue // 实现IComparable<>的接口 以便于内嵌到其他容器, 如Pair<int, MyQueue<int>>
public class MyQueue<T> : Queue<T>, IComparable<MyQueue<T>> where T : IComparable<T>
{
// 定义两个Queue是否相等的判断基准 注释掉的代码判断队列的每一个对应的元素是否相等
public int CompareTo(MyQueue<T> other)
{
/*int len = other.Count;
int thisLen = this.Count;
if (thisLen != len)
{
return thisLen - len;
}
while (len > 0)
{
if (this.Dequeue().CompareTo(other.Dequeue()) != 0)
{
return this.Peek().CompareTo(other.Peek());
}
--len;
}*/
return 0;
}
}
#endregion
}