数据类型——值类型与引用类型
通过这种方式来隐含地控制指针运行
- 值类型表示变量本身
- 引用类型本质上是地址
- 值类型与引用类型使用相同的语法来分配内存
- 所有内存的释放都不需要手工进行,CLR执行一种算法来跟踪可以访问的和不可访问的引用变量,定期清除那些不能访问的对象,并将其内存返回给操作系统
- 预定义类型除了object和string之外都为值类型
- 自定义类型除了结构(struct)和枚举(enum)之外都为引用类型
值类型:
int i;//未初始化
int j = i;//C++中允许但C#中不允许
int i = 3;//或者int i = new int();相当于int i=0;调用了int的构造函数
int j = i;//允许
引用类型:
预定义引用类型:string,object,string str=“abcd”;
自定义引用类型:(类,接口,委托,事件……)
class rpoint // 定义了一个引用类型
{ public int i;public int j;}
rpoint rp1; // 完成了定义
int i = rp1.i; // 初始化之前,既不能读
rp1.i = 3; // 也不能写。
rp1 = new rpoint(); // 初始化
int j = rp1.j; // 可以读
rp1.j = 3; // 可以写了。
struct与class
在C#中这两者的区别和C++中略有不同
- 类型:struct是值类型,class是引用类型
- 内存分配:struct在栈上分配内存
- 继承:struct不支持继承,而class支持继承
- 默认构造函数:struct默认有一个无参构造函数,而class没有
赋值方法:
值类型拷贝一个全新的数据结构
引用类型只能生成一个新的引用
rpoint rp1 = new rpoint();
rpoint rp2 = rp1;
rp1.i = 20;
//rp2.i==20 第二个也变了
vpoint rp1 = new vpoint();
vpoint rp2 = rp1;
rp1.i = 20;
//rp2.i==0 第二个不变
string:
引用类型,但表现出值类型的特性
string s1 = "abcd";
string s2 = s1;
s1 = "efg";//abcd,第二个不变
方法参数传递
回忆:
C的函数参数传递——拷贝(传值传址)
C++函数参数传递——拷贝和换名
C#中值类型是拷贝形式,引用类型是拷贝地址形式
除了string类型,任何改变字符串的企图都会导致新的字符串的创建,更像一个值类型
private GameObject solve(GameObject obj){
//对对象进行处理
return obj;
}
private void solve(GameObject obj){
//对对象进行处理
}
上者GameObject
处理过后效果其实是一样的,在C++中前一种方式似乎更符合直觉,但在C#中作为引用类型本身传入函数时就拷贝的就是地址,使用起来更为方便。
对于值类型,经常性地会出现返回值类型远远达不到我们的目的(比如需要返回多个值类型的情况下),这个时候也可以利用ref
关键字来讲值类型转变成引用类型
private void g(ref vpoint p){
p.x = 100;
}
//函数外值类型直接发生改变
out:
如果不需要使用初始值
void f(ref int p){
p = 8;
}
int a;
f(ref a);//false
int a;
a = 1;//无意义,我们适用的情况就是不需要初始值的情况
f(ref a);
//改为
void f(out int p){
p = 8;
}
int a;
f(out a);//很不错
//也可以结合定义一起使用
f(out int a);//替换f(out a);不需要int a;这个东西了
老师也提到C#如果没使用已经初始化的值会警告,这一点在日常使用的时候确实感受颇深,这也是比C++更为人性化的一点
对于引用类型,使用ref会改变整个对象指针
void f(ref rpoint p){
p = new rpoint();
}
以上程例如果没有ref
会毫无意义,因为引用类型传入函数和C++中传递指针的原理是一样的,试想如果在C++中传指针的函数内改变指针指向可行吗?很显然不行,因为传入地址,本质也是传入“地址的拷贝”,也就是指针的拷贝,改变这个指向和函数外的那个指针没任何关系。那么在C#中也是一样的,不过如果为引用类型加上ref
关键字,这个时候用C++的说法就变成“指针的引用”了,再去在函数内改变这个指针的指向,这个操作就变得有意义了。
总结:
- 值类型拷贝值
- 引用类型拷贝指针
- ref值类型拷贝指针
- ref引用类型拷贝指针的指针
预定义引用类型
Object:
所有类型都是从这个类型继承的,在程序中隐式继承
相关方法(常用于各种类型重写):
Equals
——支持对象间的比较
Finalize
——在自动回收之前进行清理操作//已删除
GetHashCode
——生成一个与对象对应的数字来支持哈希表的使用(一般用于自己重写)
ToString
——生成类实例的可读文本字符串(常用于调试)
不能妥协!要常对以上方法
override
String:
–使用 CompareTo、Equals、可进行比较。使用 IndexOf、IndexOfAny、可获取字符串中子字符串或 Unicode 字符的索引。使用 Copy 和 CopyTo 可将字符串或子字符串复制到另一个字符串或 Char 数组。
–使用 Substring 和 Split 可通过原始字符串的组成部分创建一个或多个新字符串;使用 Concat 和 Join 可通过一个或多个子字符串创建新字符串。使用 Insert、Replace、Remove、可修改字符串的全部或部分。
–使用 ToLower 和 ToUpper 可更改字符串中 Unicode 字符的大小写。使用 Format 可将字符串中一个或多个占位符替换为一个或多个值的字符串表示形式。使用 Length 属性可获取字符串中 Char 对象的数量;使用 Chars 属性可访问字符串中实际的 Char 对象。
用户自定义类型
结构(值)、枚举(值)、类(引用)、接口(引用)、委托(引用)、数组(引用)
数组:
C#支持明确的数组类型
int[] intarray;
Intarray = new int[32];
Intarray[3] = 5;
double[,] matrix = new double[10,10];
Matrix[3,4]=2;
动态数组:ArrayList;
var:
可变类型,可当作C#版的auto
- 代替具体类型,实现快速编程的隐式用法,常用在
foreach
遍历中,实质是语法糖。很典型的在遍历字典类型时,取代KeyValuePair
模板 - 和
new
一起使用,来实现一个匿名类型,用来代替需要预先创建的实体类,灵活高效 - 不能使用
var
来定义一个全局变量(预先不可知嘛) - 不能用来定义函数。包括返回值、参数类型
- 定义变量时必须先给值
- 被定义成
var
类型之后,若已经指定类型,则不能转换为其他类型
匿名类型:
例:用
var
定义匿名类
var annoyCla1 = new{
ID = 10010,
Name = "CXY",
Age = 25
};
int id = annoyCla1.ID
元组 tuple
public(int , string)f(){
return (1,"DD");
}
//这样就用不到一个类了
var a = f();
int i = a.Item1;
string s = a.Item2;
泛型类:
(欸我草走神了没听见
Params:
可变长参数,用于不确定类型以及变量数量的
//todo例子
类型转换
隐式、显式、静态、动态
回忆:
C++中隐式转换只能向上转换,就算出错也是编译出错。
显式向上转换是没必要的,显式向下转换是可以的。
隐式的向下转换有可能数据丢失,运行时不安全,可能导致崩溃。
显示向下转换可以先对类型进行识别(typeID),所以准确讲显示向下转换是安全的。
尽量用显示转换或者动态保证安全性,但不要滥用动态转换因为动态转换影响性能
System.Convert
基本类型转换
string s="123";
int si=Convert.ToInt32(s,10)//si=123;
Main函数
- public static void main(string [ ]args)