C# 教程(菜鸟到中级)

第一章 简介

##1.1 C# 有用的网站

  • C# Programming Guide - 介绍了有关关键的 C# 语言特征以及如何通过 .NET 框架访问 C# 的详细信息。
  • Visual Studio - 下载作为 C# 集成开发环境的 Visual Studio 的最新版本。
  • Go Mono - Mono 是一个允许开发人员简单地创建跨平台应用程序的软件平台。
  • C Sharp (programming language) - 维基百科解释 C#(编程语言)。

笔记

Console.Write("Hello World!");
与
Console.WriteLine("Hello World!");

**区别:**前者输出后不换行,后者输出后自动换行

这里简单介绍一下下面这段语句的作用:

Console.ReadKey();

编写 Console.Readkey(); 这个函数是为了在控制台窗口停留一下,直到敲击键盘为止。

不然运行时,“Hello World!” 这句话会在控制台窗口一闪而过,没法查看。

Console.Readkey();
//与
Console.Readline();
//与
Console.Read();

**区别:**第一个在键盘敲击任意键退出;后两个差不多,都是输入任意东西后按回车才会退出。

Console.ReadLine() 会等待直到用户按下回车,一次读入一行。

Console.ReadKey() 则是等待用户按下任意键,一次读入一个字符。

class Program
{
   private List<Student> allStu = newList<Student>();
   string yesORno;
   bool flag = true;
   
   public void AddStudent()
   {
       while (flag)
       {
           Student s = new Student();
           Console.WriteLine("请输入学生姓名:");
           s.StuName = Console.ReadLine();
           Console.WriteLine("请输入学生学号");
           s.StuNum = Console.ReadLine();
           allStu.Add(s);

           Console.WriteLine("是否继续输入:请输入y或者n");
           yesORno =Console.ReadKey().Key.ToString();
           
           switch (yesORno)
           {
               case "Y":
                   flag = true;
                   break;
               case "N":
                   flag = false;
                   break;
           }
       }
   }

   public void ShowStu()
   {
       for (int i = 0; i < allStu.Count; i++)
       {
           Console.WriteLine(allStu[i].ToString());
       }
   }

   static void Main(string[] args)
   {
       Program p = new Program();
       p.AddStudent();
       p.ShowStu();
       Console.ReadLine();
   }
}
class Student
{
   private string stuName;

   public string StuName
   {
       get { return stuName; }
       set { stuName = value; }
   }

   private string stuNum;

   public string StuNum
   {
       get { return stuNum; }
       set { stuNum = value; }
   }

   public override string ToString()
   {
       return "姓名" + stuName + "......." + "学号" + stuNum;
   }
}

输入与输出

Console.Write();      输出不换行
Console.WriteLine();  输出并换行
Console.ReadLine();   读取键盘输入的所有字符,返回字符串。按下回车键退出 
Console.Read();       读取键盘输入的第一个字符,返回其对应的ASCII值。按下回车键退出
Console.ReadKey();    等待用户按下任意键并执行退出,(此函数的作用是为了在控制台窗口停留一下,直到用户敲击键盘为止。不然运行时,"Hello World!" 这句话会在控制台窗口一闪而过,没法查看。)

.NET、C#和ASP.NET三者之间的区别如下:

一、什么是.NET?

.NET是微软公司下的一个开发平台,.NET核心就是.NET Framwork(.NET框架)是.NET程序开发和运行的环境,在这个平台下可以用不同的语言进行开发,因为.NET是跨语言的一个平台。语言可以是C#,f#,j#,vb.net等等。JAVA和.NET不同的一点是java是跨平台的,不跨语言的。.NET跨平台仅限于安卓系统和 iOS 苹果系统。

1、.net框架的组成分为两部分:

  • CLR:公共语言运行时(Common Language Runtime),提供内在管理,代码安全性检测等功能。
  • FLC:.NET框架类库(.NET Framework Class Library),提供大量应用类库,类库中有很多线程的资源可使用(比如:类,方法,属性),提高开效率。

2、CLR的结构:

  • CLS:公共语言规范,获取各种语言转换成统一的语法规范。
  • CTS:通用类型系统,将各种语言中的数据类型转换成统一的类型
  • JIT:实时编译器(即时编译器)用于将转换之后的语言编译为二进制语言,交给CPU执行。

3、.NET运行的机制流程

各种语言(c#,F#,j#等对应的源程序)——>经过CLS,CTS第一次编译——>统一规范语言(中间语言)MSIL(.EXE,.DLL)——>JIT第二次编译——>二进制语言——>运行在CPU中

java的运行机制:

java——>编译——>字节码文件(.CLASS)——>jvm解释(jvm虚拟机)——>二进制语言——>运行在CPU中

二、什么是C#?

C#是一个.NET平台下的一个程序设计语言,仅仅是一个语言。是运行在.net CLR上的,用于创建应用程序的高级语言。 程序设计语言是为了方便开发人员和计算机沟通的工具。

三、什么是ASP.NET?

ASP.NET是一个网站开发的技术,是.NET框架中的一个应用模型,用于生成基于Web的应用程序的内容丰富的编程框架。使用ASP.NET开发Web应用程序并维持其运行,比其他开发语言更加简单。与Java、PHP和Perl等高级技术相比,ASP.NET具有方便性、灵活性、生产效率高、安全性高及面向对象等优点,是目前主流的网络编程技术之一。

第二章 C# 程序结构

在我们学习 C# 编程语言的基础构件块之前,让我们先看一下 C# 的最小的程序结构,以便作为接下来章节的参考。

2.1 C# Hello World 实例

一个 C# 程序主要包括以下部分:

  • 命名空间声明(Namespace declaration)
  • 一个 class
  • Class 方法
  • Class 属性
  • 一个 Main 方法
  • 语句(Statements)& 表达式(Expressions)
  • 注释

让我们看一个可以打印出 “Hello World” 的简单的代码:

using System;
namespace HelloWorldApplication
{
   class HelloWorld
   {
      static void Main(string[] args)
      {
         /* 我的第一个 C# 程序*/
         Console.WriteLine("Hello World");
         Console.ReadKey();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

Hello World

让我们看一下上面程序的各个部分:

  • 程序的第一行 using System; - using 关键字用于在程序中包含 System 命名空间。 一个程序一般有多个 using 语句。

  • 下一行是 namespace 声明。一个 namespace 里包含了一系列的类。HelloWorldApplication 命名空间包含了类 HelloWorld

  • 下一行是 class 声明。类 HelloWorld 包含了程序使用的数据和方法声明。类一般包含多个方法。方法定义了类的行为。在这里,HelloWorld 类只有一个 Main 方法。

  • 下一行定义了 Main 方法,是所有 C# 程序的 入口点Main 方法说明当执行时 类将做什么动作。

  • 下一行 // 将会被编译器忽略,且它会在程序中添加额外的 注释

  • Main 方法通过语句 Console.WriteLine(“Hello World”); 指定了它的行为。 WriteLine 是一个定义在 System 命名空间中的 Console 类的一个方法。该语句会在屏幕上显示消息 “Hello World”。

  • 最后一行 Console.ReadKey(); 是针对 VS.NET 用户的。这使得程序会等待一个按键的动作,防止程序从 Visual Studio .NET 启动时屏幕会快速运行并关闭。
    以下几点值得注意:

  • C# 是大小写敏感的。

  • 所有的语句和表达式必须以分号(;)结尾。

  • 程序的执行从 Main 方法开始。

  • 与 Java 不同的是,文件名可以不同于类的名称。

    C# 基本语法

    C# 是一种面向对象的编程语言。在面向对象的程序设计方法中,程序由各种相互交互的对象组成。相同种类的对象通常具有相同的类型,或者说,是在相同的 class 中。

    例如,以 Rectangle(矩形)对象为例。它具有 length 和 width 属性。根据设计,它可能需要接受这些属性值、计算面积和显示细节。

    让我们来看看一个 Rectangle(矩形)类的实现,并借此讨论 C# 的基本语法:

     using System;
    
     namespace RectangleApplication
        {
            class Rectangle
            {
                // 成员变量
                double length;
                double width;
                public void Acceptdetails()
                {
                    length = 4.5;    
                    width = 3.5;
                }
                public double GetArea()
                {
                    return length * width;
                }
                public void Display()
                {
                    Console.WriteLine("Length: {0}", length);
                    Console.WriteLine("Width: {0}", width);
                    Console.WriteLine("Area: {0}", GetArea());
                }
            }
            class ExecuteRectangle
            {
                static void Main(string[] args)
                {
                    Rectangle r = new Rectangle();
                    r.Acceptdetails();
                    r.Display();
                    Console.ReadLine();
                }
            }
      }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    Length: 4.5
    Width: 3.5
    Area: 15.75
    

    using 关键字

    在任何 C# 程序中的第一条语句都是:

    using System;
    

    using 关键字用于在程序中包含命名空间。一个程序可以包含多个 using 语句。

    class 关键字

    class 关键字用于声明一个类。

    C# 中的注释

    注释是用于解释代码。编译器会忽略注释的条目。在 C# 程序中,多行注释以 /* 开始,并以字符 */ 终止,如下所示:

    /* This program demonstrates
    The basic syntax of C# programming 
    Language */
    

    单行注释是用 ‘//’ 符号表示。例如:

    }//end class Rectangle    
    

    成员变量

    变量是类的属性或数据成员,用于存储数据。在上面的程序中,Rectangle 类有两个成员变量,名为 lengthwidth

    成员函数

    函数是一系列执行指定任务的语句。类的成员函数是在类内声明的。我们举例的类 Rectangle 包含了三个成员函数: AcceptDetailsGetAreaDisplay

    实例化一个类

    在上面的程序中,类 ExecuteRectangle 是一个包含 Main() 方法和实例化 Rectangle 类的类。

    标识符

    标识符是用来识别类、变量、函数或任何其它用户定义的项目。在 C# 中,类的命名必须遵循如下基本规则:

  • 标识符必须以字母、下划线或 @ 开头,后面可以跟一系列的字母、数字( 0 - 9 )、下划线( _ )、@。
  • 标识符中的第一个字符不能是数字。
  • 标识符必须不包含任何嵌入的空格或符号,比如 ? - +! # % ^ & * ( ) [ ] { } . ; : " ’ / \。
  • 标识符不能是 C# 关键字。除非它们有一个 @ 前缀。 例如,@if 是有效的标识符,但 if 不是,因为 if 是关键字。
  • 标识符必须区分大小写。大写字母和小写字母被认为是不同的字母。
  • 不能与C#的类库名称相同。

C# 关键字

关键字是 C# 编译器预定义的保留字。这些关键字不能用作标识符,但是,如果您想使用这些关键字作为标识符,可以在关键字前面加上 @ 字符作为前缀。

在 C# 中,有些关键字在代码的上下文中有特殊的意义,如 get 和 set,这些被称为上下文关键字(contextual keywords)。

下表列出了 C# 中的保留关键字(Reserved Keywords)和上下文关键字(Contextual Keywords):

保留关键字
abstract as base bool break byte case
catch char checked class const continue decimal
default delegate do double else enum event
explicit extern false finally fixed float for
foreach goto if implicit in in (generic modifier) int
interface internal is lock long namespace new
null object operator out out (generic modifier) override params
private protected public readonly ref return sbyte
sealed short sizeof stackalloc static string struct
switch this throw true try typeof uint
ulong unchecked unsafe ushort using virtual void
volatile while
上下文关键字
add alias ascending descending dynamic from get
global group into join let orderby partial (type)
partial (method) remove select set
##笔记
  static void Main(string[] args)
  {
      Console.WriteLine("A:{0},a:{1}",65,97);
      Console.ReadLine();
  }

运行结果:

A:65,a:97

当 WriteLine() 函数有多个参数时,输出第一个参数中的内容,而第二个参数中的内容替换掉第一个参数中对应位置的占位符一起输出。

如果第一个参数没有留占位符,那么第二个参数内容不输出.

Console.WriteLine("A:,a:",65,97);

运行结果:

A:,a:

占位符从零开始计数,且占位符中的数字不能大于第二个参数的个数减一(要求占位符必须有可替换的值).

占位符数字与第二个参数字符位置一一对应.

static void Main(string[] args)
{
    Console.WriteLine("A:{1},a:{0}",65,97);
    Console.ReadLine();
}

运行结果:

A:97,a:65

C# 中什么叫类的实例化?

**直白地解释就是:**通过已有的类(class)创建出该类的一个对象(object),这一过程就叫做类的实例化。

**打个比方:**你设计了一个飞机模型,交给工人师傅去加工制作,生产出一个(一批)飞机。飞机模型就相当于程序中的类,生产出来的飞机就是对象,生产飞机这一过程就叫做类的实例化。

MyPlane  plane1= new MyPlane();        //类的实例化

补充一下 C# 中 // 注释和 /// 注的区别:

C# 引入了新的 XML 注释,即我们在某个函数前新起一行,输入 ///,VS.Net 会自动增加 XML 格式的注释

// 不会被编译,而 /// 会被编译

所以使用 /// 会减慢编译的速度(但不会影响执行速度)

但使用 /// 会在其它的人调用你的代码时提供智能感知(通常在Form。Designer.cs 窗口会自动生成 /// 注释的程序)举个例子:

/// <summary>
/// 必需的设计器变量。  //定义属性,注意该属性为可读可写属性
/// </summary>

要注意必须有:

/// <summary>
///
/// </summary>

C# 中 // 注释和 /// 注释的区别

/// 会被编译, // 不会

所以使用 /// 会减慢编译的速度(但不会影响执行速度)

/// 会在其它的人调用你的代码时提供智能感知

/// 也是一种注释,但是这种注释主要有两种作用:

  • 1.这些注释能够生成一个XML文件。这样呢,看起来会对程序的内容一目了然。
  • 2.以这种方式对你的类,属性,方法等等加上注释之后,当你在其他地方使用以这种方式加上的类,属性,方法等等地时候,黄色的提示框里面会把你写的注释显示出来,是你更加明白你要使用的功能的作用。
/// <summary>
///定义用户姓名属性,该属性为可读可写属性
/// </summary>

C# 编码的时候,常常涉及代码注释,常见的注释包括两类:

  • 1)单行注释。格式:// Comments
  • 2)多行注释。格式:/* Comments… */

C# 引入了新的 XML 注释,即我们在某个函数前新起一行,输入 ///,VS.Net 会自动增加XML格式的注释,这里整理一下可用的XML注释。 XML 注释分为一级注释(Primary Tags)和二级注释(Secondary Tags),前者可以单独存在,后者必须包含在一级注释内部。

I 一级注释

\1. 对类型进行描述,功能类似,据说建议使用;
\2. 对共有类型的类、方法、属性或字段进行注释;
\3. 主要用于属性的注释,表示属性的制的含义,可以配合使用;
\4. 用于对方法的参数进行说明,格式:value;
\5. 用于定义方法的返回值,对于一个方法,输入///后,会自动添加、列表和;
\6. 定义可能抛出的异常,格式:;
\7. 用于给出如何使用某个方法、属性或者字段的使用方法;
\8. 涉及方法的访问许可;
\9. 用于参考某个其它的东东:),也可以通过cref设置属性;
\10. 用于指示外部的XML注释;

II 二级注释

\1. or 主要用于加入代码段;
\2. 的作用类似HTML中的

标记符,就是分段;
\3. 用于引用某个参数;
\4. 的作用类似,可以指示其它的方法;
\5. 用于生成一个列表;
另外,还可以自定义XML标签
让C#智能注释时显示为换行
在C#智能注释时,常常希望它能在开发时显示为换行,使得提示更加友好!原来一直想怎么实现,今天偶然发现原来如此简单,只需将 标记用于诸如 、 或 等标记内即可。
注释在开发时换行显示的办法
标记用于诸如 、 或 等标记内,使您得以将结构添加到文本中。

/// <summary> 
/// 基类(第1行) 
///<para>说明:(第2行)</para> 
///<para>  封装一些常用的成员(第3行)</para> 
///<para>  前面要用全角空格才能显示出空格来(第4行)</para> 
/// </summary> 
public class MyBase 
{ 
      /// <summary> 
      /// 构造函数(第1行) 
      ///<para>说明:(第2行)</para> 
      ///<para>  初始化一些数据(第3行)</para> 
      /// </summary> 
      public MyBase() 
      { 
            // 
            //TODO: 在此处添加构造函数逻辑 
            // 
       } 
} 

img

class ExecuteRectangle
{
    static void Main(string[] args)
    {
        Rectangle r = new Rectangle();
        r.Acceptdetails();
        r.Display();
        Console.ReadLine();
    }
}

这段话的意义文中并没有给出解释

其实是用来声明前面 Acceptdetails() 和 Display()。

Console.ReadLine(); 是使程序在接受到输入后才结束,而不是马上结束并关掉命令行窗口。

using System;
namespace RectangleApplication
{
    class Rectangle
    {
        // 成员变量
        double length;
        double width;
        public void Acceptdetails()//顯示length = 4.5 width = 3.5
        {
            length = 4.5;    
            width = 3.5;
        }
        public double GetArea()//計算length * width數值
        {
            return length * width;
        }
        public void Display()//顯示所需要的顯示的
        {
            Console.WriteLine("Length: {0}", length);
            Console.WriteLine("Width: {0}", width);
            Console.WriteLine("Area: {0}", GetArea());
        }
    }
    
    class ExecuteRectangle
    {
        static void Main(string[] args)
        {
            Rectangle r = new Rectangle();//實體化 r
            r.Acceptdetails(); //使用Rectangle這個方法=顯示length = 4.5 width = 3.5
            r.Display();//使用Display這個方法計算length * width數值
            Console.ReadLine();
        }
    }
}

當一個方法有超過兩件事情,就分開來寫。

物件導向 & 面相對向的原理就是善用 Class 做分類。

不要將所有事情都寫在一起,到時候有問題要全部重寫。

也有可能別人在看代碼需要很多時間去理解。

因此善用分類,讓大家都能輕易讀懂。

未來 Class 一定會建立很多,可能一個 Class 就只有 2-3 行。

但這就是單一職責,高內聚 低耦合,讓未來 debug 不會這麼痛苦。

寫不難,邏輯分清楚,讓大家都能懂,這才難。

在 C# 中:

int 相当于 System.Int32
double 相当于 System.Double
strin g相当于 System.String

一般在程序开头添加 using System;,这时 string 相当于 System.String

using System;namespace RectangleApplication{
    class Rectangle
    {
        // 成员变量
        double length;//声明一个double类型的变量length 
        double width;//声明一个double类型的变量width
        public void Acceptdetails() //一个用来赋值的方法
        {
            length = 4.5;    
            width = 3.5;
        }
        public double GetArea() //一个用来计算的方法
        {
            return length * width;
        }
        public void Display()  //一个用来打印的方法
        {
            Console.WriteLine("Length: {0}", length);
            Console.WriteLine("Width: {0}", width);
            Console.WriteLine("Area: {0}", GetArea()); //打印GetArea方法的计算结果
        }
    }
    
    class ExecuteRectangle
    {
        static void Main(string[] args) //程序入口方法,创建实例,调用相应的方法
        {
            Rectangle r = new Rectangle();
            r.Acceptdetails();
            r.Display();
            Console.ReadLine();
        }
    }
}

在这段代码中,逻辑是这样的:

  • 首先进入 Main 方法,创建一个名称为 r 的实例。

  • 然后调用 Acceptdetails 给 r 进行赋值。

  • 最后调用 Display 方法打印结果。

  • 而用于计算的 GetArea 方法在在调用 Display 时直接打印出来

第三章 C# 数据类型

在 C# 中,变量分为以下几种类型:

  • 值类型(Value types)
  • 引用类型(Reference types)
  • 指针类型(Pointer types)

3.1值类型(Value types)

值类型变量可以直接分配给一个值。它们是从类 System.ValueType 中派生的。

值类型直接包含数据。比如 int、char、float,它们分别存储数字、字符、浮点数。当您声明一个 int 类型时,系统分配内存来存储值。

下表列出了 C# 2010 中可用的值类型:

类型 描述 范围 默认值
bool 布尔值 True 或 False False
byte 8 位无符号整数 0 到 255 0
char 16 位 Unicode 字符 U +0000 到 U +ffff ‘\0’
decimal 128 位精确的十进制值,28-29 有效位数 (-7.9 x 1028 到 7.9 x 1028) / 100 到 28 0.0M
double 64 位双精度浮点型 (+/-)5.0 x 10-324 到 (+/-)1.7 x 10308 0.0D
float 32 位单精度浮点型 -3.4 x 1038 到 + 3.4 x 1038 0.0F
int 32 位有符号整数类型 -2,147,483,648 到 2,147,483,647 0
long 64 位有符号整数类型 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 0L
sbyte 8 位有符号整数类型 -128 到 127 0
short 16 位有符号整数类型 -32,768 到 32,767 0
uint 32 位无符号整数类型 0 到 4,294,967,295 0
ulong 64 位无符号整数类型 0 到 18,446,744,073,709,551,615 0
ushort 16 位无符号整数类型 0 到 65,535 0

如需得到一个类型或一个变量在特定平台上的准确尺寸,可以使用 sizeof 方法。表达式 sizeof(type) 产生以字节为单位存储对象或类型的存储尺寸。下面举例获取任何机器上 int 类型的存储尺寸:

using System;

namespace DataTypeApplication
{
   class Program
   {
      static void Main(string[] args)
      {
         Console.WriteLine("Size of int: {0}", sizeof(int));
         Console.ReadLine();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

Size of int: 4

3.2引用类型(Reference types)

引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。

换句话说,它们指的是一个内存位置。使用多个变量时,引用类型可以指向一个内存位置。如果内存位置的数据是由一个变量改变的,其他变量会自动反映这种值的变化。内置的 引用类型有:objectdynamicstring

3.2.1对象(Object)类型

对象(Object)类型 是 C# 通用类型系统(Common Type System - CTS)中所有数据类型的终极基类。Object 是 System.Object 类的别名。所以对象(Object)类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。

当一个值类型转换为对象类型时,则被称为 装箱;另一方面,当一个对象类型转换为值类型时,则被称为 拆箱

object obj;
obj = 100; // 这是装箱

3.2.2动态(Dynamic)类型

您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。

声明动态类型的语法:

dynamic <variable_name> = value;

例如:

dynamic d = 20;

动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的。

3.2.3字符串(String)类型

字符串(String)类型 允许您给变量分配任何字符串值。字符串(String)类型是 System.String 类的别名。它是从对象(Object)类型派生的。字符串(String)类型的值可以通过两种形式进行分配:引号和 @引号。

例如:

String str = "runoob.com";

一个 @引号字符串:

@"runoob.com";

C# string 字符串的前面可以加 @(称作"逐字字符串")将转义字符(\)当作普通字符对待,比如:

string str = @"C:\Windows";

等价于:

string str = "C:\\Windows";

@ 字符串中可以任意换行,换行符及缩进空格都计算在字符串长度之内。

string str = @"<script type=""text/javascript"">
    <!--
    -->
</script>";

用户自定义引用类型有:class、interface 或 delegate。我们将在以后的章节中讨论这些类型。

3.3指针类型(Pointer types)

指针类型变量存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。

声明指针类型的语法:

type* identifier;

例如:

char* cptr;
int* iptr;

笔记

关于装箱和拆箱

装箱:值类型转换为对象类型, 实例:

int val = 8;
object obj = val;//整型数据转换为了对象类型(装箱)

拆箱:之前由值类型转换而来的对象类型再转回值类型, 实例:

int val = 8;
object obj = val;//先装箱
int nval = (int)obj;//再拆箱

只有装过箱的数据才能拆箱

obj 和int之间关系

using System;
namespace RectangleApplication
{
    class ExecuteRectangle
    {
        static void Main(string[] args)
        {
            int a=9;
            object obj;
            obj = a;
            obj =10;
            Console.WriteLine("2: {0}", a);   // 输出:2: 9
            Console.WriteLine("1: {0}", obj); // 输出:1: 10
            Console.ReadLine();
        }
    }
} 

设置值 int a=9; obj=a; 当obj改变不会对 int a 进行改变,object 只是复制了 int a 的值出来然后对其操作而已。不会影响到 int 原来的值。

C# 中 String 跟 string 的区别

string 是 C# 中的类,String 是 .net Framework 的类(在 C# IDE 中不会显示蓝色) C# string 映射为 .net Framework 的String 如果用 string, 编译器会把它编译成 String,所以如果直接用 String 就可以让编译器少做一点点工作。

如果使用 C#,建议使用 string,比较符合规范 string 始终代表 System.String(1.x) 或 ::System.String(2.0) ,String 只有在前面有 using System;的时候并且当前命名空间中没有名为 String 的类型(class、struct、delegate、enum)的时候才代表 System.String string 是关键字,String 不是,也就是说 string 不能作为类、结构、枚举、字段、变量、方法、属性的名称,而 String 可以。

String 是 CLR 的类型名称(也算是关键字),而 string 是 C# 中的关键字。string 在编译时候 C# 编译器会默认将其转换为 String,在这里会多增加几行转换的代码。很多时候都是建议使用 CLR 的类型而不要使用 C# 的类型(这是专家的建议)。比如说还有:使用 int 的时候最好使用 Int32 等。很多时候都是一个习惯问题,规范问题。还有一个不同就是在 VS 中表现的颜色不一样:String 是绿色,string 是蓝色。

值类型的特点:

  • 1.不能从值类型派生新类型,但可以结构实现接口;
  • 2.值类型不能包含 null 值;
  • 3.每个值类型都具有一个初始化该类型的默认值的隐式默认构造函数。

每一个值类型都有一个独立的内存区域保存自己的值,调用它的时候调用的是它的值,而引用类型调用的是内存中的地址,比如定义引用类型 a1=10,这时候在内存中保存的是 10,当把 a1 赋给 a2 的时候,他们两个应用的是同一个内存空间,a2 的值会保存为 a1 的值,当把 a2 改为 20 时,应为 a1 和 a2 引用的是同一个所以 a1 也变成 20 了,这是引用类型,值类型是当把 a1 赋给 a2 时会为 a2 在开一块新的空间保存 a1 的值。当把 a2 改成 20 时就会在 a2 的空间保存 20,和 a1 并无什么关系。

就像仓库,仓库里有货架,货架上有编号:A1,A2,A3…, 这些编号就可以看做是引用类型,现在来了一批货,有 “土豆,黄瓜,西红柿”,这些就是值类型,如果你想让 A1=土豆,那么就要把土豆搬到 A1 里面去,这就叫装箱,装箱需要耗费人力和工时(也就是耗费CPU和内存),同理拆箱就要把对应编号的货物搬出来,也是需要耗费人力和工时。

关于值类型、引用类型以及“栈”跟“堆”的关系

值类型,声明一个值类型的时候,是在“栈”中开辟一个内存空间来存放对应的值,当值类型的值发生改变的时候,则直接修改该内存空间所保存的值。例:

int n1 = 5;
int n2 = n1;
Console.WriteLine(n1 + "  "+ n2);    // 5  5
n2 = 7;
Console.WriteLine(n1 + "  " + n2)    // 5  7

这里首先在“栈”中开辟一个内存空间用来保存 n1 的值 5,接着再在“栈”中开辟一个新的内存空间用来保存 n2 的值 5,所以显示出来的结果是 5 5。然后将 n2 在“栈”中对应的内存空间保存的值修改成 7,故显示出来的结果是 5 7。

引用类型,声明一个引用类型的时候,首先是在“堆”中开辟一个内存空间来存放对应的值,然后在“栈”中开辟一个内存空间用于保存在“堆”中开辟的内存空间的地址。当系统调用引用类型的时候,首先去“栈”中获取到地址,然后根据地址在“堆”中找到对应的内存空间来获取到对应值。像数组这样的引用类型

string[] a1 = new string[]{ "a" , "b" , "c" };
string[] a2 = a1;
for(int i = 0; i < a2.Length; i++)
{
    Console.Write(a2[i] + " ");    //a b c
}
a1[2] = "d";
Console.WriteLine();            //换行
for(int i = 0; i < a2.Length; i++)
{
    Console.Write(a2[i] + " ");    //a b d
}
Console.WriteLine(); 

这里首先是在“堆”中开辟一个内存空间(假设:0X55)用来保存数组a1的值,然后在“栈”中开辟一个内存空间(a1)用于保存地址 0X55。当将 a1 赋给 a2 时,是将地址赋给 a2,即在“栈”中开辟一个内存空间(a2)用于保存地址 0X55,所以输出 a2 的值是 a b c。当将 a1[2]修改成”d”的时候,修改的是“堆”中 0X55 内存空间保存的值,因为 a2 的地址和 a1 的地址一样,所以输出结果是 a b d。

而 string 是一个特殊的引用类型,先看下面代码:

string a = "123";
string b = a; 
Console.WriteLine(a+" "+b);  //123 123
string b = "456";
Console.WriteLine(a+" "+b);  //123 456

和数组类似的,这里首先在“堆”中开辟一个内存空间(假设:0X88)用来保存 a 的值 123,然后在“栈”中开辟一个内存空间(a)用于保存地址 0X88。

和数组不同的是,当将 a 赋给 b 的时候,首先是在“堆”中开辟一个新的内存空间(假设:0X101)用于保存值 123,然后在“栈”中开辟一个内存空间(b)用于保存地址 0X101,所以输出的结果是 123 123。

当修改 b 值时,并不是修改“堆”中 0X101 内存空间的值,而是在“堆”中重新开辟一个新的内存空间(假设:0X210)用于保存 b 修改后的值,然后将 b 在“栈”中对应的内存空间的所保存的地址修改成 0X210,所以输出的结果是 123 456。而“堆”中的 0X101 内存空间将在下次的垃圾回收中被回收利用。

值类型与引用类型的区别:

从内存上看,值类型是在栈中的操作,而引用类型是在堆中的操作。

(导致 => 值类型存取速度快,引用类型存取速度慢。)

从本质上看,值类型表示实际数据,引用类型表示指向存储在内存堆中的数据的指针或引用。

(值类型是具体的那个数值所占用的空间大小,而引用类型是存放那个数值的空间地址。)

从来源上看,值类型继承自System.ValueType,引用类型继承自System.Object。

特别的:结构体是值类型,类和string是引用类型。

当一个值类型转换为对象类型时,则被称为 装箱;另一方面,当一个对象类型转换为值类型时,则被称为 拆箱

动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的。

第四章C# 类型转换

类型转换从根本上说是类型铸造,或者说是把数据从一种类型转换为另一种类型。在 C# 中,类型铸造有两种形式:

  • 隐式类型转换 - 这些转换是 C# 默认的以安全方式进行的转换, 不会导致数据丢失。例如,从小的整数类型转换为大的整数类型,从派生类转换为基类。
  • 显式类型转换 - 显式类型转换,即强制类型转换。显式转换需要强制转换运算符,而且强制转换会造成数据丢失。

下面的实例显示了一个显式的类型转换:

namespace TypeConversionApplication
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            double d = 5673.74;
            int i;
            // 强制转换 double 为 int
            i = (int)d;
            Console.WriteLine(i);
            Console.ReadKey();
        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

5673

在 C#语言中 一些预定义的数据类型之间存在着预定义的转换 比如 从 int 类
型转换到 long 类型 C#语言中数据类型的转换可以分为两类 隐式转换 implicit
conversions 和显式转换 explicit conversions 本章我们将详细介绍这两类转换

##4.1 隐式类型转换

隐式转换就是系统默认的 不需要加以声明就可以进行的转换 在隐式转换过程
中 编译器无需对转换进行详细检查就能够安全地执行转换 比如从 int 类型转换到 long
类型就是一种隐式转换 隐式转换一般不会失败 转换过程中也不会导致信息丢失
比如

  int i = 10; 
       long l = i; 

###4.1.1 隐式数值转换

隐式数值转换包括以下几种

  • 从 sbyte 类型到 short, int, long, float, double, 或 decimal 类型
  • 从 byte 类型到 short, ushort, int, uint, long, ulong, float, double, 或 decimal 类型
  • 从 short 类型到 int, long, float, double, 或 decimal 类型
  • 从 ushort 类型到 int, uint, long, ulong, float, double, 或 decimal 类型
  • 从 int 类型到 long, float, double, 或 decimal 类型
  • 从 uint 类型到 long, ulong, float, double, 或 decimal 类型
  • 从 long 类型到 float, double, 或 decimal 类型
  • 从 ulong 类型到 float, double, 或 decimal 类型
  • 从 char 类型到 ushort, int, uint, long, ulong, float, double, 或 decimal 类型
  • 从 float 类型到 double 类型

​ 隐式数值转换实 际上就是从低精度的数值类型到高精度的数值类型的转换 ,从上面的 10 条我们可以看出不存在到 char 类型的隐式转换,这意味着其它整型 值不能自动转换为 char 类型 这一点需要特别注意 。
下面的程序给出了隐式数值转换的例子

   using System; 
   class Test 
   { 
    public static void Main() 
    { 
    byte x = 16; 
       Console.WriteLine("x = {0}",x); 
    ushort y = x; 
       Console.WriteLine("y = {0}",y); 
       y = 65535; 
       Console.WriteLine("y = {0}",y); 
    float z = y; 
       Console.WriteLine("z = {0}",z);  
    } 
   } 

程序的输出将是

   x = 16; 
   y = 16; 
   y = 65535; 
   z = 65535; 

如果我们在上面程序中的语句之后再加上一句

   y = y+1; 

再重新编译程序时 编译器将会给出一条错误信息

   Can not implictly convert type 'int' to type 'ushort' 

这说明 从整数类型 65536 到无符号短整型 y 不存在隐式转换 。

###4.1.2 隐式枚举转换

隐式枚举转换允许把十进制整数 0 转换成任何枚举类型 对应其它的整数则不存
在这种隐式转换 还是让我们用例子来说明

   using System; 
   enum Weekday{ 
     Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday 
   }; 
   class Test 
   { 
    public static void Main() { 
       Weekday day; 
       day = 0; 
       Console.WriteLine(day); 
    } 
   } 

程序的输出是

   0 

但是如果我们把语句 day = 0 改写为 day = 1 编译器就会给出错误

   Can not implictly convert type 'int' to type 'enum' 

###4.1.3 隐式引用转换

隐式引用转换包括以下几类:

  • 从任何引用类型到对象类型的转换

  • 从类类型 s 到类类型 t 的转换 其中 s 是 t 的派生类

  • 从类类型 s 到接口类型 t 的转换 其中类 s 实现了接口 t

  • 从接口类型 s 到接口类型 t 的转换 其中 t 是 s 的父接口

    从元素类型为 Ts 的数组类型 S 向元素类型为 Tt 的数组类型 T 转换 这种转换需
    要满足下列条件 :

  1. S 和 T 只有元素的数据类型不同 但它们的维数相同
  2. Ts 和 Tt 都是引用类型
  3. 存在从 Ts 到 Tt 的隐式引用转换
  4. 从任何数组类型到 System.Array 的转换
  5. 从任何代表类型到 System.Delegate 的转换
  6. 从任何数组类型或代表类型到 System.ICloneable 的转换
  7. 从空类型 nul 到任何引用类型的转换

比如 :下面的程序无法通过编译 因为数组的元素类型是值类型 C#中不存在这样的隐式转换 。

   using System; 
   class Test 
   { 
    public static void Main() { 
    float[] float_ar = new float[10]; 
    int[] int_arr = new int[10]; 
       float_ar = int_ar; 
    } 
   } 

   using System; 
   class Class1 
   {} 
   class Class2 : Class1 
   {} 
   class Test 
   { 
    public static void Main() { 
    Clas1[] class1_ar = new Class1[10]; 
    Clas2[] class2_ar = new Class2[10]; 
    class1_ar = class2_ar; 
    }
 } 

 using System; 
 class Test 
 { 
  public static void Main()
  {
     float[] float_ar = new float[10]; 
     double[] double_ar = new double[10]; 
     sbyte[] sbyte_ar = new sbyte[10]; 
     byte[] byte_ar = new byte[10]; 
     ushort[] ushort_arr = new ushort[10]; 
     int[] int_arr = new int[10]; 
     long[] long_arr = newlong[10]; 
     string[] string_ar = new string[10]; 
     Console.WriteLine(float_ar); 
     Console.WriteLine(double_ar); 
     Console.WriteLine(sbyte_ar); 
     Console.WriteLine(byte_arr); 
     Console.WriteLine(ushort_arr); 
     Console.WriteLine(int_arr); 
     Console.WriteLine(long_arr); 
     Console.WriteLine(string_ar); 
  } 
 } 

程序的输出结果是

System.Single[]; 
System.Double[]; 
System.Sbyte[]; 
Syetem.Byte[]; 
  Syetem.Int16[]; 
  Syetem.Int32[]; 
  Syetem.Int64[]; 
  System.String[]; 

##4.2 显式类型转换

显式类型转换 又叫强制类型转换 与隐式转换正好相反 显式转换需要用户明 确地指定转换的类型 比如下面的例子把一个类型显式转换为类型 C

 long l = 5000; 
 int i = (int) l; 

显式转换可以发生在表达式的计算过程中 它并不是总能成功 而且常常可能引 起信息丢失。
显式转换包括所有的隐式转换 也就是说把任何系统允许的隐式转换写成显式转 换的形式都是允许的 如 :

 int i = 10; 
 long l = (long)i; 

###4.2.1 显式数值转换

显式数值转换是指当不存在相应的隐式转换时 从一种数字类型到另一种数字类
型的转换 包括 :

  • 从 sbyte 到 byte, ushort, uint, ulong, 或 char
  • 从 byte 到 sbyte 或 char
  • 从 short 到 sbyte, byte, ushort, uint, ulong, 或 char
  • 从 ushort 到 sbyte, byte, short, 或 char
  • 从 int 到 sbyte, byte, short, ushort, uint, ulong, 或 char
  • 从 uint 到 sbyte, byte, short, ushort, int, 或 char
  • 从 long 到 sbyte, byte, short, ushort, int, uint, ulong, 或 char
  • 从 ulong 到 sbyte, byte, short, ushort, int, uint, long, 或 char
  • 从 char 到 sbyte, byte, 或 short
  • 从 float 到 sbyte, byte, short, ushort, int, uint, long, ulong, char, 或 decimal
  • 从 double 到 sbyte, byte, short, ushort, int, uint, long, ulong, char, float, or 或
  • 从 decimal 到 sbyte, byte, short, ushort, int, uint, long, ulong, char, float, 或 double

这种显式转换有可能丢失信息或导致异常抛出 转换按照下列规则进行 对于从一种整型到另一种整型的转换 编译器将针对转换进行溢出检测 如 果没有发生溢出 转换成功 否则抛出一个 OverflowException 异常 这种检测还与编 译器中是否设定了 checked 选项有关 对于从 float, double, 或 decimal 到整型的转换 源变量的值通过舍入得到最接
近的整型值作为转换的结果 如果这个整型值超出了目标类型的值域 则将抛出一个 OverflowException 异常 对从 double 到 float 的转换 double 值通过舍入取最接近的 float 值 如果这 个值太小 结果将变成正 0 或负 0 如果这个值太大 将变成正无穷或负无穷 如果原 double 值是 NaN 则转换结果也是 NaN 对于从 float 或 double 到 decimal 的转换 源值将转换成小数形式并通过舍入 取到小数点后 28 位 如果有必要的话 如果源值太小 则结果为 0 如果太大以致不 能用小数表示 或是无穷和 NaN 则将抛出 InvalidCastException 异常 对于从 decimal 到 float 或 double 的转换 小数的值通过舍入取最接近的值 这种转换可能会丢失精度 但不会引起异常

  using System; 
 
       class Test 
       { 
            static void Main() { 
                    long longValue = Int64.MaxValue; 
                    int intValue = (int) longValue; 
                    Console.WriteLine("(int) {0} = {1}", longValue, intValue); 
            } 
       } 

这个例子把一个 int 类型转换成为 long 类型 输出结果是

 (int) 9223372036854775807 = -1 

这是因为发生了溢出 从而在显式类型转换时导致了信息丢失

###4.2.2 显式枚举转换

显式枚举转换包括以下内容 从 sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, 或 decimal
到任何枚举类型从任何枚举类型到 sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, 或 decimal 从任何枚举类型到任何其它枚举类型 显式枚举转换是这样进行的 它实际上是枚举类型的元素类型与相应类型之间的 隐式或显式转换 比如 有一个元素类型为 int 的枚举类型 E 则当执行从 E 到 byte 的显式枚举转换时 实际上作的是从 int 到 byte 的显式数字转换 当执行从 byte 到 E 的显式枚举转换时 实际上是执行 byte 到 int 的隐式数字转换

     using System; 
     enum Weekday{ 
       Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday 
     };  
     class Test  
     {  
      public static void Main() {  
         Weekday day; 
         day = (Weekday)3; 
         Console.WriteLine(day);  
      }  
     } 

程序的输出是

3 

###4.2.3 显式引用转换

  • 从对象到任何引用类型

  • 从类类型 S 到类类型 T 其中 S 是 T 的基类

  • 从类类型 S 到接口类型 T 其中 S 不是密封类 而且没有实现 T

  • 从接口类型 S 到类类型 T 其中 T 不是密封类 而且没有实现 S

  • 从接口类型 S 到接口类型 T 其中 S 不是 T 的子接口

    从元素类型为 Ts 的数组类型 S 到元素类型为 Tt 的数组类型 T 的转换 这种转换

需要满足下列条件

  1. S 和 T 只有元素的数据类型不同 而维数相同

  2. Ts 和 Tt 都是引用类型

  3. 存在从 Ts 到 Tt 的显式引用转换 .

  4. 从 System.Array 到数组类型

  5. 从 System.Delegate 到代表类型

  6. 从 System.ICloneable 到数组类型或代表类型
    显式引用转换发生在引用类型之间 需要在运行时检测以确保正确 为了确保显式引用转换的正常执行 要求源变量的值必须是 nul 或者它所引用的 对象的类型可以被隐式引用转换转换为目标类型 否则显式引用转换失败 将抛出一 个 InvalidCastException 异常
    不论隐式还是显式引用转换 虽然可能会改变引用值的类型 却不会改变值本身

4.3 C# 类型转换方法

C# 提供了下列内置的类型转换方法:

序号 方法 & 描述
1 ToBoolean 如果可能的话,把类型转换为布尔型。
2 ToByte 把类型转换为字节类型。
3 ToChar 如果可能的话,把类型转换为单个 Unicode 字符类型。
4 ToDateTime 把类型(整数或字符串类型)转换为 日期-时间 结构。
5 ToDecimal 把浮点型或整数类型转换为十进制类型。
6 ToDouble 把类型转换为双精度浮点型。
7 ToInt16 把类型转换为 16 位整数类型。
8 ToInt32 把类型转换为 32 位整数类型。
9 ToInt64 把类型转换为 64 位整数类型。
10 ToSbyte 把类型转换为有符号字节类型。
11 ToSingle 把类型转换为小浮点数类型。
12 ToString 把类型转换为字符串类型。
13 ToType 把类型转换为指定类型。
14 ToUInt16 把类型转换为 16 位无符号整数类型。
15 ToUInt32 把类型转换为 32 位无符号整数类型。
16 ToUInt64 把类型转换为 64 位无符号整数类型。

下面的实例把不同值的类型转换为字符串类型:

namespace TypeConversionApplication
{
    class StringConversion
    {
        static void Main(string[] args)
        {
            int i = 75;
            float f = 53.005f;
            double d = 2345.7652;
            bool b = true;
            Console.WriteLine(i.ToString());
            Console.WriteLine(f.ToString());
            Console.WriteLine(d.ToString());
            Console.WriteLine(b.ToString());
            Console.ReadKey();
        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

75
53.005
2345.7652
True

##笔记

隐式转换和显式转换

隐式转换:C# 默认的以安全方式进行的转换。本质是从小存储容量数据类型自动转换为大存储容量数据类型,从派生类转换为基类。

实例:

namespace TypeConvertion
{   class Class1
    {

    }

    class Class2 : Class1 //类Class2是类Class1的子类
    {

    }
    class Program
    {
        static void Main(string[] args)
        {
            int inum = 100;
            long lnum = inum; // 进行了隐式转换,将 int 型(数据范围小)数据转换为了 long 型(数据范围大)的数据
            Class1 c1 = new Class2(); // 这里也是隐式转换,将一个新建的 Class2 实例转换为了其基类 Class1 类型的实例 C1
        }
    }
}

显式转换:通过用户使用预定义的函数显式完成的,显式转换需要强制转换运算符。

转换类型的范围大小和从属关系和隐式转换相反。显式转换可能会导致数据出错,或者转换失败,甚至无法编译成功。

double dnum = 100.1;
int ifromd = (int)dnum; //double类型显式转换转为int类
Class1 c11 = new Class1();
Class2 c22 = c11 as Class2; //使用as进行显式转换
Console.WriteLine(c22 is Class1);
Console.WriteLine(c22 is Class2);

运行结果:

FALSE
FALSE

类型之间的转换 - Convert 和 Parse

string locstr = 123.ToString();

//如果要将"locstr"转成整型数

//方法一: 用 Convert 
int i = Convert.ToInt16(locstr);

//方法二: 用 Parse
int ii = int.Parse(locstr);
int.TryParse(string s,out int i)

该方式也是将数字内容的字符串转换为int类型,但是该方式比int.Parse(string s) 好一些,它不会出现异常,最后一个参数result是输出值,如果转换成功则输出相应的值,转换失败则输出0。

class test
{
    static void Main(string[] args)
    {
        string s1="abcd";
        string s2="1234";
        int a,b;
        bool bo1=int.TryParse(s1,out a);
        Console.WriteLine(s1+" "+bo1+" "+a);
        bool bo2=int.TryParse(s2,out b);
        Console.WriteLine(s2+" "+bo2+" "+b);
    }
}

结果输出:

abcd False 0
1234 True 1234

C# 中对 double 类型的数据取整,可以使用 convert.toint32() 方法,也可使用 int 强制转换为整数,使用 int 时并不存在四舍五入的情况,而是直接将后面的小数位数丢掉。比如:

class Program
{
    static void Main(string[] args)
    {
        double a = 1.35;
        double b = 1.65;
        int a1 = Convert.ToInt32(a);
        int a2 = (int)(a);
        int b1 = Convert.ToInt32(b);
        int b2 = (int)(b);
        Console.WriteLine("{0}使用convert方法转化的结果为:{1}",a,a1);
        Console.WriteLine("{0}使用int强制转换的结果为:{1}",a,a2);
        Console.WriteLine("{0}使用convert方法转化的结果为:{1}", b, b1);
        Console.WriteLine("{0}使用int强制转换的结果为:{1}", b, b2);
        Console.ReadKey();
    }
}

程序运行结果如下:

1.35使用convert方法转化的结果为:1
1.35使用int强制转换的结果为:1
1.65使用convert方法转化的结果为:2
1.65使用int强制转换的结果为:1

Convert.ToInt32() 与 int.Parse() 的区别

没搞清楚 Convert.ToInt32 和 int.Parse() 的细细微区别时千万别乱用,否则可能会产生无法预料的结果,举例来说:假如从 url 中取一个参数 page 的值,我们知道这个值是一个 int,所以即可以用 Convert.ToInt32(Request.QueryString[“page”]),也可以用 int.Parse(Request.QueryString[“page”]),但是如果 page 这个参数在 url 中不存在,那么前者将返回 0,0 可能是一个有效的值,所以你不知道 url 中原来根本就没有这个参数而继续进行下一下的处理,这就可能产生意想不到的效果,而用后一种办法的话没有 page 这个参数会抛出异常,我们可以捕获异常然后再做相应的处理,比如提示用户缺少参数,而不是把参数值当做 0 来处理。

(1) 这两个方法的最大不同是它们对 null 值的处理方法: Convert.ToInt32(null) 会返回 0 而不会产生任何异常,但 int.Parse(null) 则会产生异常。

(2) 对数据进行四舍五入时候的区别

  • a. Convert.ToInt32(double value) 如果 value 为两个整数中间的数字,则返回二者中的偶数;即 3.5 转换为 4,4.5 转换为 4,而 5.5 转换为 6。不过 4.6 可以转换为 5,4.4 转换为 4 。
  • b. int.Parse(“4.5”) 直接报错:“输入字符串的格式不正确”
  • c. int(4.6) = 4 Int 转化其他数值类型为 Int 时没有四舍五入,强制转换。

(3) 对被转换类型的区别 int.Parse 是转换 String 为 int, Convert.ToInt32 是转换继承自 Object 的对象为 int 的(可以有很多其它类型的数据)。你得到一个 object 对象, 你想把它转换为 int, 用 int.Parse 就不可以, 要用 Convert.ToInt32。

浅谈 string 转 int 与抛异常

string 字符串类型和 int 也是可以转换的。下一行的代码给出错误的转换方法。

string a = "123";  // 将a设置为字符串“123”
int x = (int) a;   // 转换

上述代码,毋庸置疑,肯定是错误的。VS 在编译时就过不了。那么,string 该怎么转换成 int 呢?

这里,我们需要用到 int.Parse(),核心代码为:

string a = "123";     // 将a设置为字符串“123”
int x = int.Parse(a); // 转换

如果仅仅是这样,是没有问题的,但是,我们下面再来做一个实例。

用户输入一个数字,而电脑将计算出这个数字加上1以后的答案,并且显示出来。

用户输入的东西,即 Console.ReadLine() ,一定是以字符串形式表现的。

于是,运用之前的方法,我们可以写出以下的代码:

class 测试
{
    static void Main(string[] args)
    {
        Console.WriteLine("输入数字,将计算出它加一的答案");
        int a = int.Parse(Console.ReadLine());  //转换用户输入的数字
        Console.WriteLine("答案是{0}",++a);     //++a 即 a+1 后的那个数,将它输出出来
        Console.ReadKey();
    }
}

当程序运行时,会出现:

输入数字,将计算出它加一的答案
3
答案是4

这样就很完美了吗?不!!

如果用户输入并非数字的其他字符,如汉字,会发生什么情况?

此时,用户输入 王 ,显然,程序将无法继续运行下去,因为int类型只能存整数,不能存字符。

这时,程序就会抛出异常。

如果用 VS 编,你还会看到异常类型:FormatException。

所以,为了保险,可以用try、catch来解决此问题。核心代码为:

try
{
               
}
catch (Exception)
{
                
}

try 在英语中就是尝试的意思。在这段代码中,try{} 部分,顾名思义,也就是去尝试进行下面的代码。catch{} 部分,则是检测异常。这样,在出现异常时,catch 就能捕获到异常,从而程序并不会停止。

则这段程序,完整的代码应该为:

using System;
class 测试
{
    static void Main(string[] args)
    {
        try
        {
           Console.WriteLine("输入数字,将计算出它加一的答案");
           int a = int.Parse(Console.ReadLine());   //有可能会抛出异常
           Console.WriteLine("答案是{0}",++a);   //如果没有异常,程序才会进入这一步
        }
        catch (Exception)
        {
            Console.WriteLine("无法转换");  //如果捕获到异常,就说“无法转换”
        }
         Console.ReadKey();
    }
}

这样,如果我输入了 王 ,程序结果为:

无法转换

**1)**对于转换对象,Convert.ToInt32() 可以为多种类型(例出数字类型外 bool,DateTime 等),int.TryParse() 和 int.Parse() 只能是整型字符串类型(即各种整型 ToString() 之后的形式,不能为浮点型,否则 int.Parse() 就会出现输入的字符串格式不正确的错误,int.TryParse() 也会返回 false,输出参数为 0 ,(int)只能是数字类型(例 float,int,uint等);

**2)**对于空值 NULL,从运行报错的角度讲,(int) 强制转换和 int.Parse() 都不能接受 NULL;Convert.ToInt32() 其实是在转换前先做了一个判断,参数如果为 NULL,则直接返回 0,否则就调用 int.Parse() 进行转换,int.TryParse() 其实是对 int.Parse() 做了一个异常处理,如果出现异常则返回 false,并且将输出参数返回 0;

**3)**针对于浮点型的取舍问题,浮点型只有 Convert.ToInt32() 和 (int) 能进行转换,但是也是进行取舍了的,Convert.ToInt32() 采取的取舍是进行四舍五入,而 (int) 则是截取浮点型的整数部分,忽略小数部分,例如 Convert.ToInt32(1.499d) 和 (int)1.499d 都返回 1,Convert.ToInt32(1.5d) 返回 2,而 (int)1.5d 还是返回 1;

**4)**关于溢出,将大的数据类型转换为小的数据类型时 Convert.ToInt32() 和 int.Parse() 都会报溢出错误,值对于 Int32 太大或太小,而 (int) 不报错,但是返回值为 -1。

如此可见,我们在进行数据转换前选择转换方法要谨慎,如果是数字类型可以考虑直接用(int)强制转换,如果是整型字符串类型的,考虑用 int.Parse() 进行转换,如果不是这两种类型,再考虑用 Convert.ToInt32() 进行转换。

第五章C# 变量

一个变量只不过是一个供程序操作的存储区的名字。在 C# 中,每个变量都有一个特定的类型,类型决定了变量的内存大小和布局。范围内的值可以存储在内存中,可以对变量进行一系列操作。

我们已经讨论了各种数据类型。C# 中提供的基本的值类型大致可以分为以下几类:

类型 举例
整数类型 sbyte、byte、short、ushort、int、uint、long、ulong 和 char
浮点型 float 和 double
十进制类型 decimal
布尔类型 true 或 false 值,指定的值
空类型 可为空值的数据类型

C# 允许定义其他值类型的变量,比如 enum,也允许定义引用类型变量,比如 class。这些我们将在以后的章节中进行讨论。在本章节中,我们只研究基本变量类型。

5.1 C# 中的变量定义

C# 中变量定义的语法:

<data_type> <variable_list>;

在这里,data_type 必须是一个有效的 C# 数据类型,可以是 char、int、float、double 或其他用户自定义的数据类型。variable_list 可以由一个或多个用逗号分隔的标识符名称组成。

一些有效的变量定义如下所示:

int i, j, k;
char c, ch;
float f, salary;
double d;

您可以在变量定义时进行初始化:

int i = 100;

5.2 C# 中的变量初始化

变量通过在等号后跟一个常量表达式进行初始化(赋值)。初始化的一般形式为:

variable_name = value;

变量可以在声明时被初始化(指定一个初始值)。初始化由一个等号后跟一个常量表达式组成,如下所示:

<data_type> <variable_name> = value;

一些实例:

int d = 3, f = 5;    /* 初始化 d 和 f. */
byte z = 22;         /* 初始化 z. */
double pi = 3.14159; /* 声明 pi 的近似值 */
char x = 'x';        /* 变量 x 的值为 'x' */

正确地初始化变量是一个良好的编程习惯,否则有时程序会产生意想不到的结果。

请看下面的实例,使用了各种类型的变量:

namespace VariableDefinition
{
    class Program
    {
        static void Main(string[] args)
        {
            short a;
            int b ;
            double c;
            /* 实际初始化 */
            a = 10;
            b = 20;
            c = a + b;
            Console.WriteLine("a = {0}, b = {1}, c = {2}", a, b, c);
            Console.ReadLine();
        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

a = 10, b = 20, c = 30

5.3接受来自用户的值

System

命名空间中的

Console

类提供了一个函数

ReadLine()

,用于接收来自用户的输入,并把它存储到一个变量中。

例如:

int num;
num = Convert.ToInt32(Console.ReadLine());

函数 Convert.ToInt32() 把用户输入的数据转换为 int 数据类型,因为 Console.ReadLine() 只接受字符串格式的数据。

5.4C# 中的 Lvalues 和 Rvalues

C# 中的两种表达式:

  1. lvalue:lvalue 表达式可以出现在赋值语句的左边或右边。
  2. rvalue:rvalue 表达式可以出现在赋值语句的右边,不能出现在赋值语句的左边。

变量是 lvalue 的,所以可以出现在赋值语句的左边。数值是 rvalue 的,因此不能被赋值,不能出现在赋值语句的左边。下面是一个有效的语句:

int g = 20;

下面是一个无效的语句,会产生编译时错误:

10 = 20;

笔记

不同类型变量进行运算的问题:

double a = 42.29;
int b = 4229;
int c = a + b;
Console.WriteLine("c = {0}",c);
Console.ReadKey();

上面这种编程方法是错误的,会出现错误提示:

"无法将类型'double'隐式转换为'int'。"

举例说明,当一个精度高的数据类型与一个精度低的数据类型进行运算时,定义运算结果的变量类型必须与精度最高的变量类型相同。这是为了防止在运算过程中造成数据丢失。

下面是正确代码:

double a = 42.29;
int b = 4229;
double c = a + b;
Console.WriteLine("c = {0}",c);
Console.ReadKey();

能输出运算结果:

c = 4271.29

关于静态变量

在 C# 中没有全局变量的概念,所有变量必须由该类的实例进行操作,这样做提升了安全性,但是在某些情况下却显得力不从心。

因此,我们在保存一些类的公共信息时,就会使用静态变量。

static <data_type> <variable_name> = value;

在变量之前加上 static 关键字,即可声明为静态变量。

方法的局部变量必须在代码中显式初始化,之后才能在语句中使用它们的值。此时,初始化不是在声明该变量时进行的,但编译器会通过方法检查所有可能的路径,如果检测到局部变量在初始化之前就使用了它的值,就会产生错误。

例如:

public static int Main(){
    int d;
    Console.WriteLine(d);
}

在这段代码中,演示了如何定义 Main(),使之返回一个 int 类型的数据,而不是 void。但在编译这些代码时,会得到下面的错误消息:

 Use of unassigned local variable 'd' 

正确的做法是初始化它 int d = 0 或者其他值。

第六章C# 常量

常量是固定值,程序执行期间不会改变。常量可以是任何基本数据类型,比如整数常量、浮点常量、字符常量或者字符串常量,还有枚举常量。

常量可以被当作常规的变量,只是它们的值在定义后不能被修改。

6.1整数常量

整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,没有前缀则表示十进制。

整数常量也可以有后缀,可以是 U 和 L 的组合,其中,U 和 L 分别表示 unsigned 和 long。后缀可以是大写或者小写,多个后缀以任意顺序进行组合。

这里有一些整数常量的实例:

212         /* 合法 */
215u        /* 合法 */
0xFeeL      /* 合法 */
078         /* 非法:8 不是一个八进制数字 */
032UU       /* 非法:不能重复后缀 */

以下是各种类型的整数常量的实例:

85         /* 十进制 */
0213       /* 八进制 */
0x4b       /* 十六进制 */
30         /* int */
30u        /* 无符号 int */
30l        /* long */
30ul       /* 无符号 long */

6.2浮点常量

一个浮点常量是由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。

这里有一些浮点常量的实例:

3.14159       /* 合法 */
314159E-5L    /* 合法 */
510E          /* 非法:不完全指数 */
210f          /* 非法:没有小数或指数 */
.e55          /* 非法:缺少整数或小数 */

使用小数形式表示时,必须包含小数点、指数或同时包含两者。使用指数形式表示时,必须包含整数部分、小数部分或同时包含两者。有符号的指数是用 e 或 E 表示的。

6.3字符常量

字符常量是括在单引号里,例如,‘x’,且可存储在一个简单的字符类型变量中。一个字符常量可以是一个普通字符(例如 ‘x’)、一个转义序列(例如 ‘\t’)或者一个通用字符(例如 ‘\u02C0’)。

在 C# 中有一些特定的字符,当它们的前面带有反斜杠时有特殊的意义,可用于表示换行符(\n)或制表符 tab(\t)。在这里,列出一些转义序列码:

转义序列 含义
\ \ 字符
’ 字符
" " 字符
? ? 字符
\a Alert 或 bell
\b 退格键(Backspace)
\f 换页符(Form feed)
\n 换行符(Newline)
\r 回车
\t 水平制表符 tab
\v 垂直制表符 tab
\ooo 一到三位的八进制数
\xhh . . . 一个或多个数字的十六进制数

以下是一些转义序列字符的实例:

namespace EscapeChar
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello\tWorld\n\n");
            Console.ReadLine();
        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

Hello   World

6.4字符串常量

字符串常量是括在双引号 “” 里,或者是括在 @“” 里。字符串常量包含的字符与字符常量相似,可以是:普通字符、转义序列和通用字符

使用字符串常量时,可以把一个很长的行拆成多个行,可以使用空格分隔各个部分。

这里是一些字符串常量的实例。下面所列的各种形式表示相同的字符串。

string a = "hello, world";                  // hello, world
string b = @"hello, world";               // hello, world
string c = "hello \t world";               // hello     world
string d = @"hello \t world";               // hello \t world
string e = "Joe said \"Hello\" to me";      // Joe said "Hello" to me
string f = @"Joe said ""Hello"" to me";   // Joe said "Hello" to me
string g = "\\\\server\\share\\file.txt";   // \\server\share\file.txt
string h = @"\\server\share\file.txt";      // \\server\share\file.txt
string i = "one\r\ntwo\r\nthree";
string j = @"one
two
three";

6.5定义常量

常量是使用 const 关键字来定义的 。定义一个常量的语法如下:

const <data_type> <constant_name> = value;

下面的代码演示了如何在程序中定义和使用常量:

using System;
public class ConstTest
{
    class SampleClass
    {
        public int x;
        public int y;
        public const int c1 = 5;
        public const int c2 = c1 + 5;
        public SampleClass(int p1, int p2)
        {
            x = p1;
            y = p2;
        }
    }
    static void Main()
    {
        SampleClass mC = new SampleClass(11, 22);
        Console.WriteLine("x = {0}, y = {1}", mC.x, mC.y);
        Console.WriteLine("c1 = {0}, c2 = {1}",
                          SampleClass.c1, SampleClass.c2);
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

x = 11, y = 22
c1 = 5, c2 = 10

笔记

1、Convert.ToDouble 与 Double.Parse 的区别。实际上 Convert.ToDouble 与 Double.Parse 较为类似,实际上 Convert.ToDouble内部调用了 Double.Parse:

(1)对于参数为null的时候:

  • Convert.ToDouble参数为 null 时,返回 0.0;
  • Double.Parse 参数为 null 时,抛出异常。

(2)对于参数为""的时候:

  • Convert.ToDouble参数为 “” 时,抛出异常;
  • Double.Parse 参数为 “” 时,抛出异常。

(3)其它区别:

  • Convert.ToDouble可以转换的类型较多;
  • Double.Parse 只能转换数字类型的字符串。
  • Double.TryParse 与 Double.Parse 又较为类似,但它不会产生异常,转换成功返回 true,转换失败返回 false。最后一个参数为输出值,如果转换失败,输出值为 0.0。

附测试代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                //string a = "0.2";
                //string a = null;
                string a = "";
                try
                {
                    double d1 = Double.Parse(a);
                }
                catch (Exception err)
                {
                    Console.WriteLine("d1转换出错:" + err.Message);
                }

                try
                {
                    double d2 = Convert.ToDouble(a);
                }
                catch (Exception err)
                {
                    Console.WriteLine("d2转换出错:" + err.Message);

                }
                try
                {
                    double d3;
                    Double.TryParse(a,out d3);
                }
                catch (Exception err)
                {
                    Console.WriteLine("d3转换出错:" + err.Message);
                }
            }
            finally
            {
                Console.ReadKey();
            }

        }
    }
}

2.Convert.ToInt32() 与 int.Parse() 的区别

没搞清楚Convert.ToInt32和int.Parse()的细细微区别时千万别乱用,否则可能会产生无法预料的结果,举例来说:假如从url中取一个参数page的值,我们知道这个值是一个int,所以即可以用Convert.ToInt32(Request.QueryString[“page”]),也可以用,int.Parse(Request.QueryString[“page”]),但是如果page这个参数在url中不存在,那么前者将返回0,0可能是一个有效的值,所以你不知道url中原来根本就没有这个参数而继续进行下一下的处理,这就可能产生意想不到的效果ÿ

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值