第一章 C#基础知识
1.掌握基本变量的类型
C#语言的数据类型分为值类型和引用类型
区别:值类型的变量直接存储数据,引用类型的变量持有数据的引用,数据存储在数据堆中。
值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
值类型包括:整型,浮点型,字符型,布尔型,枚举型,结构体;
引用类型包括:类,接口,数组,委托,字符串string,Object等。
有符号整型:sbyte, short, int, long
无符号整型:byte, ushort, uint, ulong
占用字节数: 1 2 4 8
浮点型:
float: 单精度,占4个字节,最多保留7位小数
Double: 双精度,占8字节,最多保留16位小数
字符型:char 占2个字节
字符串类型: string 字符串类型必须使用双引号括起来
布尔类型: bool 只有两个值 true 和false
****************数据类型转换***********************
1)隐式转换:系统默认,无需指明
例:int i = 1;
long j = i; //int类型隐式转换为long类型
byte,short,char=>int=>long=>float=>double
2)显式转换(强制类型转换):需要在代码中明确声明要转换的类型
例: int i = 2;
long j = (long) i; //int类型强制类型转换为long类型
注意:强制类型转换时在把值范围较大的数据类型转换为值范围较小的数据类型时可能会导致溢出错误!
例:double m = 3333333333333.6;
int n = (int) m; //会有溢出错误
可用checked检查转换是否安全,当发生溢出时系统会抛出一个异常,提示“算数运算导致溢出”
例:double m = 3333333333333.6;
int n = checked((int) m);
3)装箱与拆箱
装箱:将值类型转换为引用类型
原理:对值类型进行装箱转换时,会在内存堆中分配一个对象实例,并将该值复制到该对象中。
例:int i = 123;
object o = i; //装箱转换
i = 456; //改变i的内容
打印: i = 456;
o = 123;
拆箱:将引用类型转换值类型
原理:对引用类型进行拆箱转换时,需要使用强制操作符,将放在堆中的引用类型的值复制到栈中形成值类型,
分为两个阶段:(1)检查引用类型变量,确认它是否包装了值类型的数;(2)把引用类型的变量的值复制到值类型的变量中。
例:int i = 123;
object o = i; //装箱转换
o = 456; //改变o的内容
i = (int) o; //拆箱转换
装箱前i的值为123
拆箱后i的值为456
如何避免不必要的装箱/拆箱?
可以通过重载函数,接口函数,泛型来避免。
4)Convert类:将一个基本数据类型转换为另一个基本数据类型,返回与指定类型的值等效的类型
Convert类的所有方法都是静态的,可直接调用。
形式为Convert.ToXXX(xxx),即将参数xxx转换为XXX类型。
例:string str = "123";
int i = Convert.ToInt32(str);//这是将string类型转换为int类型的
*******************************************************
2.运算符及其优先级
算数运算符:+ - * / %
逻辑运算符:&& || !
比较运算符:== != > < >= <=
位运算符:&(按位与) |(按位或) ~(按位取反) ^(按位异或) <<(左移) >>(右移) >>>(无符号右移)
三元运算符: bool表达式?表达式1:表达式2
赋值运算符:= += -= *= /= %= ++ --
优先级:
. > () >[]
+(正)>-(负)>++>-->~>!
*>/>%>+>-
<< >> >>>
< <= > >=
== !=
& | ^ && || ?:
3.基本的集合类
1)列表
//定义一个列表list并初始化,使用new分配空间
List<int> list = new List<int>();
//增
list.Add(元素值);
list.AddRange(序列); //将一个序列添加到list
int f = list.First();//返回第一个元素
int l = list.Last(); //返回最后一个元素
list.Insert(2, 99);//将元素插入到指定索引处
int[] arr = new int[4] { 20, 30, 40, 50 };
list.InsertRange(4, arr);//将集合插入到指定索引处
int m1 = list.Max();//返回元素中最大值
int m2 = list.Min();//返回元素中最小值
list.sort(); //排序。默认为升序
list.TrimExcess(); //如果元素数小于当前容量的90%,则将容量设置为当前实际元素数
//删
list.Remove(元素值);//按值删除
list.RemoveAt(下标);//按下标删除
//查
list.Contains(元素值) //用contains查 返回值为bool类型 有则返回true 无则返回false
//使用遍历查
int h = 2;
for (int i = 0; i < list.Count; ++i)
{
if (h == list[i])
Console.WriteLine("YES");
}
//改
int index = 5;
int value = 8;
if (index < 0 || index >= list.Count)
{
Console.WriteLine("列表长度为:" + list.Count + ",下标输入错误!");
}
else
{
list[index] = value;
}
//倒序
list.Reverse();
//查找值的序号
int k = list.IndexOf(4); //indexof找到返回下标,没找到返回-1
int k2 = list.LastIndexOf(4);//找到匹配元素的最后一个返回其下标,没找到返回-1
2)队列
//集合实现了先进先出的机制,即元素将在集合的尾部添加、在集合的头部移除
//创建一个队列实例
Queue<int> que = new Queue<int>();
//入队
que.Enqueue(值);
int num = que.Count();//获取队列的元素个数
int f = que.Peek();//返回第一个元素但并不删除即并不出队
que.Concat(que1)//连接两个序列
int[] carr = new int[10];
que.CopyTo(carr,3); //从指定的数组索引开始,将队列复制到现有的一维数组中
int ni = que.ElementAt(2); //返回序列中指定索引处的元素
bool b1 = que.Equals(que2);//判断两个序列是否相等 true or false
que.GetType();//获取当前实例的类型
int n = (int) que.Dequeue(); //返回并移除位于 Queue 实例开始处的对象
int n = (int) que.peek() //返回位于 Queue 实例开始处的对象但不将其移除
//查找
bool b = que.Contains(值);
//复制到一个数组中
object [] a = que.ToArray();
//清除队列
que.Clear();
3)栈
//栈是一种先进后出的结构,即元素从栈的尾部插入,从栈的尾部移除。
//创建一个栈实例
Stack<int> sta = new Stack<int>();
//向栈中添加元素PUSH
sta.Push(值);
sta.Concat(sta1);//连接两个序列
sta.CopyTo(Array,index); //从指定的数组索引开始,将序列中的元素复制到指定的一维数组中
int num = sta.ElementAt(2);//返回序列中指定索引处的元素
int a = sta.Pop();//用于获取栈顶元素的值,并移除栈顶元素的值
int p = sta.Peek();//用于获取栈顶元素的值,但不移除栈顶元素的值
//查询元素是否在栈中
bool b1 = sta.Contains(元素值);
//将栈中的元素全部复制到一个数组中
int[] arr = sta.ToArray();
//清空栈
sta.Clear();
4)哈希表
//在 Hashtable 中存放了两个数组,一个数组用于存放 key 值,一个数组用于存放 value 值
//元素类型为DictionaryEntry类型。
//创建一个哈希表实例
Hashtable ht = new Hashtable();
Hashtable tsht = Hashtable.Synchronized(new Hashtable());//创建线程安全的哈希表
// 增
ht.Add("key","value");
Hashtable ht1 = (Hashtable)ht.Clone(); //创建浅表副本,需将object类型强制类型转换
bool b9 = ht.Equals(ht1);//判断两个哈希表是否相等
bool b10 = ht.IsReadOnly;//判断哈希表是否为只读
int num1 = ht.Count; //获取哈希表键值对数目
//根据键来获取键值
string st1 = (string)ht["key"];
//判断哈希表是否包含特定键或键值
bool b1 = ht.ContainsKey("key");
bool b3 = ht.ContainsValue("value");
//删除特定键值对
ht.Remove("key");
//清空哈希表
ht.Clear();
//打印哈希表的两种方法
法一 foreach(DictionaryEntry de in ht)
{
Console.Write(de.Key+" ");
Console.WriteLine(de.Value);
}
法二 ICollection ke1 = ht.Keys;
foreach (var kee in ke1)
{
Console.WriteLine(kee + " " + ht[kee]);
}
5)字典
元素类型为KeyValuePair类型。
//创建并实例一个字典
Dictionary<int, string> dic = new Dictionary<int, string>();
//添加元素
dic.Add(key, value );
//通过KeyValuePair遍历元素
foreach (KeyValuePair<int, string> kvp in dic)
{
Console.WriteLine("key:{0},value:{1}", kvp.Key, kvp.Value);
}
//获取键值对的数目
int num = dic.Count;
//判断键的存在
if( dic.ContainsKey(key))
//按指定键删除值
bool b1 = dic.Remove(key);
//获取键的集合
foreach (int k in dic.Keys)
{
Console.WriteLine("key = {0}", k);
}
//获取值的集合
foreach (string v in dic.Values)
{
Console.WriteLine("Value = {0}", v);
}
//使用TryGetValue方法获取指定键对应的值
string tgv = string.Empty;
dic.TryGetValue(4, out tgv);
//清空字典
dic.Clear();
********哈希表Hashtable与字典Dictionary的区别******
1)单线程程序中推荐使用 Dictionary, 有泛型优势, 且读取速度较快, 容量利用更充分.
2)多线程程序中推荐使用 Hashtable, 默认的 Hashtable 允许单线程写入, 多线程读取, 对 Hashtable 进一步调用 Synchronized()方法可以获得完全线程安全的类型. 而Dictionary 非线程安全, 必须人为使用 lock 语句进行保护, 效率大减.
3)Dictionary 有按插入顺序排列数据的特性 (注: 但当调用 Remove() 删除过节点后顺序被打乱), 因此在需要体现插入顺序的情境中使用 Dictionary 能获得一定方便,而Hashtable不一定按照插入的顺序排列数据。
4)哈希表最大的优势在于其索引方式,它是经过散列处理过的,在数据量大的时候尤其如此。
4.预编译指令
1)基本规则
(1)预处理指令必须和C#代码在不同的行
(2)与C#语句不同,预处理指令不需要以分号结尾
(3)包含预处理指令的每一行必须与 ‘’#‘’ 字符开始(在#字符前可以有空格,在#字符和指令之间也可以有空格)
(4)允许行尾注释
(5)在预处理指令所在的行不允许有分隔符注释,即/* */
2)常用预编译指令
#define 标识符 //定义
#undef 标识符 //删除定义
条件编译指令
#if 编译符号或表达式
#elif 编译符号或表达式
#else
#endif
注意: (1)编译符号为使用#define定义或未定义的标识符
(2)表达式为使用符号和操作符!、==、!=、&&、|| 构建的
(3)#if和#endif指令在条件编译结构中必须配对使用,只要有#if指令,就必须有#endif配对
诊断指令:诊断指令产生用户自定义的编译时的警告或错误消息
#warning 字符串 //警告
#error 字符串 //错误
注意:字符串与普通的C#字符串不同,不需要被引号包围
example: #error Can't build this code
#warning Remember to come back to check this code
行号指令
#line
(1)改变由编译器警告和错误消息报告的出现行数
(2)改变被编译源文件的文件名
(3)对交互式调试器隐藏一些行
#line 200 "Special" //设置下一行的行号值为200,并将编译源文件的名字改成“Special”
int i;
int j;
#line default //重新保存实际的行号和文件名
char c;
float f;
#line hidden // 在断点调试器中隐藏代码
string s;
double d;
#line //停止在调试器中隐藏代码
注意:
a.要改变外观文件名,可以在双引号内使用文件名作为参数,双引号是必须的
b.要返回真是的文件名和行号,可以使用default参数
c.要对交互调试器的断点调试功能隐藏代码段,可以使用hidden参数。要停止隐藏,可以使用不带任何参数的指令
d.#line hidden 指令能对调试程序隐藏连续行,当开发者逐行执行代码时,介于 #line hidden 和下一 #line 指令(假设它不是其他 #line hidden 指令)间的任何行都将被跳过。它不影响错误报告中的文件名或行号。也就是说,如果在隐藏块中遇到错误,编译器将报告错误的当前文件名和行号。
区域指令:可展开或折叠的代码块
#region
(代码块)
#endregion
注意:
(1)#region 块必须通过 #endregion 指令终止。
(2)#region 块不能与 #if块重叠。 但是,可以将 #region 块嵌套在 #if 块内,或将 #if 块嵌套在 #region 块内。
(3)#region指令后的可选字符串文本作为其名字
#pragma warning指令:#pragma warning指令允许我们关闭或重新开启warning指令
#pragma warning disable (可加行号) //关闭警告消息
#pragma warning restore (可加行号) //开启警告消息
静态构造与其他构造方法的区别:
1)静态构造函数不能带任何参数,一个类只能有一个静态构造函数;
构造函数参数数目不定,可以根据需求设定。一个类可以有多个构造函数(重载)
2)静态构造函数的执行具有不定性,你只能确保静态构造函数在第一次调用类的成员之前执行,并且静态构造函数至多执行一次。
构造函数:只要创建类的实例,都会执行,具有时间的可把握性。
3)静态构造函数没有修饰符
注意:无参数的实例构造函数可以在类中与静态构造函数安全共存。尽管参数列表是相同的,但这并不矛盾,因为静态构造函数是在加载类时执行的,而实例构造函数是在创建实例时执行,所以构造函数的执行的不会有冲突的。