C# Primitive Types 基本数据类型
0.值类型和引用类型区别
从概念上看:值类型直接存储其值,引用类型存储对值的引用。
存储在内存的不同地方:值类型存储在堆栈中,引用类型存储在托管堆上。
// 值类型
int a = 1;
int b = a;
Console.WriteLine($"a={a},b={b}"); // => a=1,b=2
b = 2;
Console.WriteLine($"Change b value : a={a},b={b}"); // => a=1,b=2
// 引用类型 A
A x = new A();
x.Value = 0;
A y = x;
Console.WriteLine($"x.Value={x.Value},y.Value={y.Value}"); //=> 0,0
y.Value = 1;
Console.WriteLine($"x.Value={x.Value},y.Value={y.Value}"); // => 1,1
1.预定义值类型
整型:sbyte(8位有符号整数),short(16),int(32),long(64),byte(8位无符号整数),ushort,uint,ulong
浮点类型:float,double
decimal 类型:表示更高精度浮点类型
bool 类型:(true,false)
char 类型:字符类型,单引号
2.预定义引用类型
object 类型:
string 类型:string 类型如果用以下代码输出结果和我们期待的引用类型得到的输出结果相反,甚至会觉得string类型是值类型。
当s1被初始化时,分配string对象,同时s2也指向该对象,当s2改变时,在堆上会为新值分配一个新对象,此时s2指向新对象,s1仍指向原对象。
因此:重复修改同一个给定的字符串效率会非常低下。(表面上修改字符串内容,实际上是创建一个新的字符串)
对字符串的替换、追加、删除字符串中的文本可以使用System.Text.StringBuilder类
// string 类型
string s1 = "hello";
string s2 = s1;
Console.WriteLine($"s1={s1},s2={s2}"); // => hello,hello
s2 = "world";
Console.WriteLine($"s1={s1},s2={s2}"); // => hello,world
// System.Text.StringBuilder 创建
var s3 = new StringBuilder("hello");
// 追加 Append()/AppendFormat()/Insert()
s3.Append(",world");
// 删除 Remove()/Replace()
s3.Replace("wo", "");
3.数组和元组
数组合并相同类型的对象,是引用类型;
简单数组:
声明和初始化:
1.int[] tArray = new int[4]; //知道元素个数
2.int[] tArray = new int[]{1,2,3,4,5}; // 知道具体元素
3.int[] tArray = {1,2,3,4,5}; // 同上
访问数组:
int t1 = tArray[1]; // 通过索引器访问 索引从0开始,错误索引会抛异常
更改数组值:
tArray[2] = 6;
多维数组:声明数组后。其阶数就确定了。
二维数组:int[,] = new int[3,3];
三维数组:int[,] = new int[3,3,3];
锯齿数组:
int[][] jagged = new int[3][];
jagged[0] = new int[2]{1,2};
jagged[1] = new int[3]{1,2,3};
jagged[2] = new int[1]{1};
复制数组:CloneI()和Copy()
Clone元素的类型是值类型,会复制所有的值;Clone元素的类型是引用类型,只复制引用。
Clone()方法创建一个新数组,Copy()方法必须传递阶数相同且有足够元素的已有数组。
数组内部元素排序:Array类使用QuickSort算法对数组中的元素进行排序。(Sort())
如果数组中的元素是自定类时:
1.该自定义类就必须继承IComparable接口并实现接口方法CompareTo()。(可以修改自定义类的情况下)
2.实现接口IComparer接口。(不能修改在数组中用作元素的类)
ArraySegment:
表示数组中的一段,不复制原数组元素,数组段中数据改变,原数组数组也改变。
// 数组 Clone()
int[] arr1 = { 1,2,3,4,5,6,7,8,9,0 };
int[] arr3 = (int[])arr1.Clone(); // 必须要强制转换,Clone()的类型是object
// Copy(source,target,length)
int[] arr4 = new int[10];
Array.Copy(arr1,arr4,10);
// ArraySegment<T>
var segment = new ArraySegment<int>(arr1, 0, 3);
segment.Array[0] = 2;// => arr1={2,2,3,4,5,6,7,8,9,0}
元组合并不同类型的对象,.NET Framework 定义了8个泛型Tuple类和1个静态Tuple类
创建元组:Tuple.Create(1, “me”);
元组项超过8个:可以使用带8个参数的Tuple类定义。
// 元组包含的项超过8个时
var tuple = Tuple.Create<string, string, string, string, int, int, double, Tuple<int, int>>("a", "b", "c", "d", 1, 2, 1.02, Tuple.Create(3, 4));
4.类
类和结构都是创建对象的模板,类是引用类型,结构是值类型,结构不支持继承。
类中的成员分两类:静态成员和实例成员(静态成员属于类,实例成员属于对象)。
1.字段:与类相关的变量,最好把字段设置为private,使用属性来访问字段。
// 类PhoneCustomer字段
class PhoneCustomer
{
public const string DayOfWeek = "Monday";
private int CustomerID;
private string FirstName;
private string LastName;
...//etc
}
2.属性:
自动实现的属性:public string FirstName{get;set;}
完整属性:
//命名约定1 此处firstName 是 FirstName的后备变量
private string firstName;
public string FirstName{get{return firstName;} set{firstName = value;}}
//命名约定2 采用下划线作为前缀
private string _firstName;
public string FirstName{get{return _firstName;} set{_firstName= value;}}
3.方法:
调用方法:
Math 类中 GetSquareOf和GetPi是静态成员,属于类,所以可以直接用Math.GetSquareOf(1) 和 Math.GetPi()。
实例成员需要实例化一个对象 var math = new Math() 来调用实例成员 math.Value = 1,math.GetSquare() // => 1
public class Math
{
public int Value;
public int GetSquare() => Value * Value;
public static int GetSquareOf(int value) => value * value;
public static double GetPi() => 3.1415926;
}
重载:签名不同(方法名相同但参数个数或者参数类型不同)。
4.常量:const 关键词来声明常量,通常用public修饰符使得可以在类的外部访问。
5.构造函数:
声明一个与 包含的类同名的方法,该方法没有返回值,且没有必要给类提供构造函数,因为编译器会自动生成一个默认的构造
函数,如果提供了带参数的构造函数,那么就不会自动生成一个没有参数的构造函数。
构造函数中调用同一个类的其他构造函数:this 关键字调用参数最匹配的那个构造函数。
构造函数中调用基类的构造函数:用 base 关键字。
// 构造函数初始化器
class Math
{
private int _a;
private int _b;
public Math(int a, int b) {
_a = a;
_b = b;
}
public Math(int a) : this(a, 0)
{
}
}
静态构造函数:
1.一个类中最多只有一个静态构造函数。
2.静态构造函数没有访问修饰符和参数。
3.实例构造函数只要创建类的对象就会执行构造函数,静态构造函数最多执行一次,通常在调用类的任何成员前执行。
4.静态构造函数只能访问类的静态成员,不能访问类的实例成员。
5.若多个类都有静态构造函数,静态构造函数的执行顺序不确定。
5.结构
结构是值类型,不支持继承,若没有提供默认构造函数,编译器会自动提供一个并把成员初始化为其默认值。
当A为结构时:结构按值传递,ChangeA 方法中的变量a得到堆栈中a1变量的一个副本,在ChangeA中的修改和销毁副本,a1的值从来没有改变过。
// 按值传递
static void Main(string[] args)
{
A a1 = new A { X = 1 };
ChangeA(a1); // 传递的a1参数是a1的一个副本
Console.WriteLine($"a1.X = {a1.X}"); // => a1.X = 1
Console.ReadLine();
}
public struct A
{
public int X { get; set; }
}
public static void ChangeA(A a)
{
a.X = 2;
}
ref参数
通过引用传递结构类型参数:
// 引用传递结构 ref
static void Main(string[] args)
{
A a1 = new A { X = 1 };
ChangeA(ref a1); // 此时与类类型相同
Console.WriteLine($"a1.X = {a1.X}"); // a1.X = 2
Console.ReadLine();
}
public struct A
{
public int X { get; set; }
}
public static void ChangeA(ref A a)
{
a.X = 2;
}
通过引用传递类类型参数:
// 引用类型传递
static void Main(string[] args)
{
A a1 = new A { X = 1 };
ChangeA(a1); // 按引用传递,ChangeA方法结束没有引用堆上的新对象
Console.WriteLine($"a1.X = {a1.X}"); // => a1 = 2;
Console.ReadLine();
}
public class A
{
public int X { get; set; }
}
public static void ChangeA(A a)
{
a.X = 2;
a = new A { X = 3 }; // 在堆上创建一个新对象
}
通过引用传递类类型参数:
static void Main(string[] args)
{
A a1 = new A { X = 1 };
ChangeA(ref a1); // 传递对引用的引用
Console.WriteLine($"a1.X = {a1.X}"); // a1.X = 3;
Console.ReadLine();
}
public class A
{
public int X { get; set; }
}
public static void ChangeA(ref A a)
{
a.X = 2;
a = new A { X = 3 };
}