值类型:值类型变量可以直接分配给一个值。它们是从类 System.ValueType 中派生的。
类型 | 描述 | 范围 | 默认值 |
---|---|---|---|
bool | 布尔值 | True 或 False | False |
byte | 8为无符号整数 | 0 到 255 | 0 |
char | 16为 Unicode 字符 | U +0000 到 U +ffff | ‘\0’ |
decimal | 128 位精确的十进制值,28-29 有效位数 | (-7.9 x 10^28 到 7.9 x 10^28) / 100^28 | 0.0M |
double | 64 位双精度浮点型 | (+/-)5.0 x 10^-324 到 (+/-)1.7 x 10^308 | 0.0D |
float | 32 位单精度浮点型 | -3.4 x 10^38 到 + 3.4 x 10^38 | 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 |
unit | 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(type) 方法。
using System;
namespace DataTypeApplication
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Size of int: {0}", sizeof(int));
Console.ReadLine();
}
}
}
引用类型:不包含存储在变量中的实际数据,但它们包含对变量的引用。换句话说,它们指的是一个内存位置。
类型 | 描述 |
---|---|
Object | 对象类型,C#通用类型系统中所有类型的终极基类 |
Dynamic | 动态类型,与对象类型相似(编译时发生),类型检查在运行时发生 |
String | 字符串类型,有两种形式进行分配:引号和@引号,"123"或 @“123” |
Class | 类 |
interface | 接口 |
Delegate | 委托 |
指针类型变量存储另一种类型的内存地址,即内存位置的直接地址。就像其他变量或常量,您必须在使用指针存储其他变量地址之前声明指针。
声明指针类型的语法:type* identifier;
char* cptr;
int* iptr;
实例 | 描述 |
---|---|
int* p | p 是指向整数的指针。 |
double* p | p 是指向双精度数的指针。 |
float* p | p 是指向浮点数的指针。 |
int** p | p 是指向整数的指针的指针。 |
int*[] p | p 是指向整数的指针的一维数组。 |
char* p | p 是指向字符的指针。 |
void* p | p 是指向未知类型的指针。 |
// C# 中使用了 unsafe 修饰符时指针的使用
static unsafe void Main(string[] args)
{
int var = 20;
int* p = &var;
Console.WriteLine("Data is: {0} ", var);
Console.WriteLine("Address is: {0}", (int)p);
Console.ReadKey();
//打印
Data is: 20
Address is: 99215364
}
//不用声明整个方法作为不安全代码,只需要声明方法的一部分作为不安全代码。
//使用 ToString() 方法检索存储在指针变量所引用位置的数据
public static void Main()
{
unsafe
{
int var = 20;
int* p = &var;
Console.WriteLine("Data is: {0} " , var);
Console.WriteLine("Data is: {0} " , p->ToString());
Console.WriteLine("Address is: {0} " , (int)p);
}
Console.ReadKey();
//打印
Data is: 20
Data is: 20
Address is: 77128984
}
//使用指针访问数组元素
//指针变量 p,因为它在内存中不是固定的,但是数组地址在内存中是固定的,使用 fixed 关键字来固定指针。
public unsafe static void Main()
{
int[] list = {10, 100, 200};
fixed(int *ptr = list)
/* 显示指针中数组地址 */
for ( int i = 0; i < 3; i++)
{
Console.WriteLine("Address of list[{0}]={1}",i,(int)(ptr + i));
Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));
}
Console.ReadKey();
//打印
Address of list[0] = 31627168
Value of list[0] = 10
Address of list[1] = 31627172
Value of list[1] = 100
Address of list[2] = 31627176
Value of list[2] = 200
}
当一个值类型转换为对象类型时,则被称为 装箱;另一方面,当一个对象类型转换为值类型时,则被称为 拆箱。
object obj;
obj = 100; // 这是装箱
int num;
num = (int)obj;//这是拆箱
一个变量只不过是一个供程序操作的存储区的名字。在 C# 中,每个变量都有一个特定的类型,类型决定了变量的内存大小和布局。范围内的值可以存储在内存中,可以对变量进行一系列操作。
C# 中提供的基本的值类型大致可以分为以下几类:
类型 | 举例 |
---|---|
整数类型 | sbyte、byte、short、ushort、int、uint、long、ulong和char |
浮点类型 | float、double |
十进制类型 | decimal |
布尔类型 | p 是指向整数的指针的指针。 |
空类型 | p 是指向整数的指针的一维数组。 |
C# 中的 Lvalues 和 Rvalues
- lvalue:lvalue 表达式可以出现在赋值语句的左边或右边。
- rvalue:rvalue 表达式可以出现在赋值语句的右边,不能出现在赋值语句的左边。
变量是 lvalue 的,所以可以出现在赋值语句的左边。数值是 rvalue 的,因此不能被赋值,不能出现在赋值语句的左边。
= value;
int d = 3, f = 5; 初始化 d 和 f.
- 常量是固定值,程序执行期间不会改变。常量可以是任何基本数据类型,比如整数常量、浮点常量、字符常量或者字符串常量,还有枚举常量。
- 常量可以被当作常规的变量,只是它们的值在定义后不能被修改。
- 整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数: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 */
//浮点常量
3.14159 /* 合法 */
314159E-5L /* 合法 */
510E /* 非法:不完全指数 */
210f /* 非法:没有小数或指数 */
.e55 /* 非法:缺少整数或小数 */
//字符串常量
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";
字符常量
转义序列 | 含义 |
---|---|
\ | \ |
’ | ’ |
" | " |
? | ? |
\a | Alert 或 bell |
\b | 退格键 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制符表 tab |
\v | 垂直制符表 tab |
\oo | 一到三位的八进制数 |
\xhh | 一个或多个数字的十六进制数 |
常量是使用 const 关键字来定义的 。定义一个常量的语法如下:
const= value;
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
假设变量 A 的值为 10,变量 B 的值为 20,则:
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加 | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
– | 自减运算符,整数值减少 1 | A-- 将得到 9 |
假设变量 A 的值为 10,变量 B 的值为 20,则:
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 不为真。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 不为真。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 不为真。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
假设变量 A 为布尔值 true,变量 B 为布尔值 false,则:
运算符 | 描述 | 实例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
|| | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:
p | q | p&q | p|q | p^q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
假设变量 A 的值为 60,变量 B 的值为 13,则:
运算符 | 描述 | 实例 |
---|---|---|
& | 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。 | (A & B) 将得到 12,即为 0000 1100 |
| | 如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中。 | (A | B) 将得到 61,即为 0011 1101 |
^ | 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中。 | (A ^ B) 将得到 49,即为 0011 0001 |
~ | 按位取反运算符是一元运算符,具有"翻转"位效果,即0变成1,1变成0,包括符号位。 | (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。 |
<< | 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。 | A << 2 将得到 240,即为 1111 0000 |
>> | 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。 | A >> 2 将得到 15,即为 0000 1111 |
下表列出了 C# 支持的赋值运算符:
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
>>= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
<<= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
|= | 按位或且赋值运算符 | C |= 2 等同于 C = C | 2 |
if(boolean_expression)
{
/* 如果布尔表达式为真将执行的语句 */
}
if(boolean_expression)
{
/* 如果布尔表达式为真将执行的语句 */
}
else
{
/* 如果布尔表达式为假将执行的语句 */
}
if( boolean_expression 1)
{
/* 当布尔表达式 1 为真时执行 */
if(boolean_expression 2)
{
/* 当布尔表达式 2 为真时执行 */
}
}
switch(expression){
case constant-expression :
statement(s);
break;
case constant-expression :
statement(s);
break;
/* 您可以有任意数量的 case 语句 */
default : /* 可选的 */
statement(s);
break;
}
switch(ch1)
{
case 'A':
printf("这个 A 是外部 switch 的一部分" );
switch(ch2)
{
case 'A':
printf("这个 A 是内部 switch 的一部分" );
break;
case 'B': /* 内部 B case 代码 */
}
break;
case 'B': /* 外部 B case 代码 */
}
int a = 1, b = 2;
string result = a > b ? "a大于b" : "a小于b";
while(condition)
{
statement(s);
}
using System;
namespace Loops
{
class Program
{
static void Main(string[] args)
{
/* for 循环执行 */
for (int a = 10; a < 20; a = a + 1)
{
Console.WriteLine("a 的值: {0}", a);
}
/* foreach 循环执行 */
int[] fibarray = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 };
foreach (int element in fibarray)
{
Console.WriteLine(element);
}
Console.ReadLine();
}
}
}
using System;
namespace Loops
{
class Program
{
static void Main(string[] args)
{
/* 局部变量定义 */
int a = 10;
/* do 循环执行 */
do
{
Console.WriteLine("a 的值: {0}", a);
a = a + 1;
} while (a < 20);
Console.ReadLine();
}
}
}
/* 嵌套 for 循环 */
for ( init; condition; increment )
{
for ( init; condition; increment )
{
statement(s);
}
statement(s);
}
/* 嵌套 while 循环 */
while(condition)
{
while(condition)
{
statement(s);
}
statement(s);
}
/* 嵌套 do...while 循环 */
do
{
statement(s);
do
{
statement(s);
}while( condition );
}while( condition );
- 当 break 语句出现在一个循环内时,循环会立即终止,且程序流将继续执行紧接着循环的下一条语句。
- 它可用于终止 switch 语句中的一个 case。
- continue 会跳过当前循环中的代码,强迫开始下一次循环。
- 对于 for 循环,continue 语句会导致执行条件测试和循环增量部分。
- 对于 while 和 do…while 循环,continue 语句会导致程序控制回到条件测试上。
- 数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,通常认为数组是一个同一类型变量的集合。
- 数组中某个指定的元素是通过索引来访问的。
- 所有的数组都是由连续的内存位置组成的。
- 最低的地址对应第一个元素,最高的地址对应最后一个元素。
- 声明一个数组不会在内存中初始化数组。当初始化数组变量时,您可以赋值给数组。
- 数组是一个引用类型,所以您需要使用 new 关键字来创建数组的实例。
double[] balance = new double[10];
//下标赋值
double[] balance = new double[10];
balance[0] = 4500.0;
//声明的同时赋值
double[] balance = { 2340.0, 4523.69, 3421.0};
//创建并初始化一个数组
int [] marks = new int[5] { 99, 98, 92, 97, 95};
//省略数组的大小
int [] marks = new int[] { 99, 98, 92, 97, 95};
//目标和源会指向相同的内存位置
int [] marks = new int[] { 99, 98, 92, 97, 95};
int[] score = marks;
double salary = balance[9];
int [] n = new int[10];/* n 是一个带有 10 个整数的数组 */
/* 初始化数组 n 中的元素 */
for ( int i = 0; i < 10; i++ )
{
n[i] = i + 100;
}
/* 输出每个数组元素的值 */
foreach (int j in n )
{
int i = j-100;
Console.WriteLine("Element[{0}] = {1}", i, j);
}
二维数组
string [,] names;
多维数组最简单的形式是二维数组。一个二维数组,在本质上,是一个一维数组的列表。
一个二维数组可以被认为是一个带有 x 行和 y 列的表格。下面是一个二维数组,包含 3 行和 4 列:
因此,数组中的每个元素是使用形式为 a[ i , j ] 的元素名称来标识的,其中 a 是数组名称,i 和 j 是唯一标识 a 中每个元素的下标。
/* 初始化 */
int [,] a = new int [3,4] {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
/* 访问 */
int val = a[2,3];
三维数组
int [ , , ] m;
Array 类是 C# 中所有数组的基类,它是在 System 命名空间中定义。Array 类提供了各种用于数组的属性和方法。
Array的一些用法
using System;
namespace ArrayApplication
{
class MyArray
{
static void Main(string[] args)
{
int[] list = { 34, 72, 13, 44, 25, 30, 10 };
Console.Write("原始数组: ");
foreach (int i in list)
{
Console.Write(i + " ");
}
Console.WriteLine();
// 逆转数组
Array.Reverse(list);
Console.Write("逆转数组: ");
foreach (int i in list)
{
Console.Write(i + " ");
}
Console.WriteLine();
// 排序数组
Array.Sort(list);
Console.Write("排序数组: ");
foreach (int i in list)
{
Console.Write(i + " ");
}
Console.WriteLine();
Console.ReadKey();
}
}
}
//原始数组: 34 72 13 44 25 30 10
//逆转数组: 10 30 25 44 13 72 34
//排序数组: 10 13 25 30 34 44 72
区别
- 数字Array,内存中是连续存储的,索引快,缺点:必须声明长度,两个数据中插入数据非常困难。
- ArrayList,集成IList接口,根据存储的数据动态扩充和收缩,声明不需要指定长度。缺点:将所有数据当做object类型,通常在存储的时候会发生装箱和拆箱操作,浪费性能。
using System;using System.Collections;
namespace CollectionApp
{
class MyComparer : IComparer
{
public int Compare(object x, object y)
{
// 自定义比较规则
// 如果x,y都是int,那么按正常流程比较
// 如果其中一个不是int,那么认为不是int的值小
// 如果都不是int,那么认为他们相等
if (x is int)
{
if (y is int)
{
if ((int)x < (int)y) return -1;
if ((int)x == (int)y) return 0;
return 1;
}
return 1;
}
if (y is int) return -1;
return 0;
}
}
class Entry
{
static void Print(ArrayList l)
{
Console.WriteLine("\t大小能力:{0} 实际大小:{1}", l.Capacity, l.Count);
Console.Write("\t内容:");
foreach (object obj in l)
Console.Write("{0} ", obj);
Console.Write("\r\n");
}
static void Main()
{
ArrayList l = new ArrayList();
Console.WriteLine("添加一些不都是Int的数据:");
l.Add(22);
l.Add(77);
l.Add("abc");
l.Add(33);
Print(l);
Console.WriteLine("排序:");
l.Sort(new MyComparer());
Print(l);
Console.ReadKey();
}
}
}
/*
添加一些不都是Int的数据:
大小能力:4 实际大小:4
内容:22 77 abc 33
排序:
大小能力:4 实际大小:4
内容:abc 22 33 77
*/
enum <enum_name>
{
enumeration list
};
enum Days
{
Sun,
Mon,
tue,
Wed,
thu,
Fri,
Sat
};
static void Main()
{
int x = (int)Day.Sun;
int y = (int)Day.Fri;
Console.WriteLine("Sun = {0}", x);
Console.WriteLine("Fri = {0}", y);
//遍历
foreach (var item in Enum.GetNames(typeof(Days)))
{
Debug.Log(item);
}
}
//转int
static void Main()
{
int x = (int)Day.Sun;
int y = (int)Day.Fri;
Console.WriteLine("Sun = {0}", x);
Console.WriteLine("Fri = {0}", y);
}
//转string
static void Main()
{
Days day = Days.Sun;
string s = day.ToString();
Console.WriteLine(s);
}
if (Enum.IsDefined(typeof(Days),1))
{
Debug.Log("ID为1");
}
string stra;
string strb;
//区分大小写
Debug.Log(string.Compare(stra, strb));
string str = "abcDEF";
str=str.Replace("abc","ABC");//"ABCDEF"
string stra;
string strb;
// +
Debug.Log(stra+strb);
// join
Debug.Log(string.Join("分隔符", stra, strb));
// Concat
Debug.Log(string.Concat(stra, strb));
// StringBuilder
StringBuilder MyStringBuilder = new StringBuilder();
MyStringBuilder.Append(stra);
MyStringBuilder.Append(strb);
Debug.Log(MyStringBuilder.ToString());
Debug.Log(stra.Contains(strb));
Debug.Log(string.Copy(strb));
Debug.Log(stra.EndsWith("a"));
Debug.Log(stra.StartsWith("123"));
Debug.Log(string.Equals(stra, strb));
Debug.Log(string.Format("字符串1:{0},字符串2:{1}", stra, strb));
Debug.Log(stra.IndexOf("a"));
Debug.Log(string.IsNullOrEmpty(stra));
Debug.Log(stra.Split('2'));
Debug.Log(stra.Insert(1, "Insert"));
Debug.Log(stra.Remove(2, 2));
Debug.Log(stra.ToUpper());
Debug.Log(stra.ToLower());
Debug.Log(stra.Trim());
Debug.Log(stra.ToCharArray());
命名空间的设计目的是提供一种让一组名称与其他名称分隔开的方式。在一个命名空间中声明的类的名称与另一个命名空间中声明的相同的类的名称不冲突。
using System;
namespace first_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside first_space");
}
}
}
namespace second_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside second_space");
}
}
}
class TestClass
{
static void Main(string[] args)
{
first_space.namespace_cl fc = new first_space.namespace_cl();
second_space.namespace_cl sc = new second_space.namespace_cl();
fc.func();
sc.func();
Console.ReadKey();
}
}
//Inside first_space
//Inside second_space
//1.using指令:引入命名空间
namespace namespace_name
{
// 代码声明
}
//2.using static 指令:指定无需指定类型名称即可访问其静态成员的类型
using static System.Math;// 直接使用System.Math.PI
void Start()
{
var = PI;
}
//3.起别名
using Project = PC.MyCompany.Project;
//4.using语句:将实例与代码绑定
using (Font font3 = new Font("Arial", 10.0f),font4 = new Font("Arial", 10.0f))
{
// Use font3 and font4.
}
//代码段结束时,自动调用font3和font4的Dispose方法,释放实例。
namespace namespace_name1
{
// 代码声明
namespace namespace_name2
{
// 代码声明
}
}
类的定义是以关键字 class 开始,后跟类的名称。类的主体,包含在一对花括号内。下面是类定义的一般形式:
``C#
<access specifier> class class_name
{
// member variables
<access specifier> <data type> variable1;
<access specifier> <data type> variable2;
...
<access specifier> <data type> variableN;
// member methods
<access specifier> <return type> method1(parameter_list)
{
// method body
}
<access specifier> <return type> method2(parameter_list)
{
// method body
}
...
<access specifier> <return type> methodN(parameter_list)
{
// method body
}
}
注意:
- 访问标识符 指定了对类及其成员的访问规则。如果没有指定,则使用默认的访问标识符。类的默认访问标识符是 internal,成员的默认访问标识符是 private。
- 数据类型 指定了变量的类型,返回类型 指定了返回的方法返回的数据类型。
- 如果要访问类的成员,你要使用点(.)运算符。
- 点运算符链接了对象的名称和成员的名称。
类和结构具有表示其数据和行为的成员。 类的成员包括在类中声明的所有成员,以及在该类的继承层次结构中的所有类中声明的所有成员(构造函数和析构函数除外)。 基类中的私有成员被继承,但不能从派生类访问。
下表列出类或结构中可包含的成员类型:
成员 | 描述 |
---|---|
字段 | 字段是在类范围声明的变量。 字段可以是内置数值类型或其他类的实例。 例如,日历类可能具有一个包含当前日期的字段。 |
常量 | 常量是在编译时设置其值并且不能更改其值的字段。 |
属性 | 属性是类中可以像类中的字段一样访问的方法。 属性可以为类字段提供保护,以避免字段在对象不知道的情况下被更改。 |
方法 | 方法定义类可以执行的操作。 方法可接受提供输入数据的参数,并可通过参数返回输出数据。 方法还可以不使用参数而直接返回值。 |
事件 | 事件向其他对象提供有关发生的事情(如单击按钮或成功完成某个方法)的通知。 事件是使用委托定义和触发的。 |
运算符 | 重载运算符被视为类型成员。 重载运算符时,将其定义为类型中的公共静态方法。 |
索引器 | 使用索引器可以用类似于数组的方式为对象建立索引。 |
构造函数 | 构造函数是首次创建对象时调用的方法。 它们通常用于初始化对象的数据。 |
析构函数 | 它们通常用来确保任何必须释放的资源都得到适当的处理。 |
嵌套类型 | 嵌套类型是在其他类型中声明的类型。 嵌套类型通常用于描述仅由包含它们的类型使用的对象。 |
class Box
{
public void SetLength(double len)
{
length = len;
}
public void SetBreadth(double bre)
{
breadth = bre;
}
public void SetHeight(double hei)
{
height = hei;
}
}
- 类的 构造函数 是类的一个特殊的成员函数,当创建类的新对象时执行。构造函数的名称与类的名称完全相同,它没有任何返回类型。
- 默认的构造函数没有任何参数。
- 参数化构造函数,构造函数可以有参数。这种技术可以帮助你在创建对象的同时给对象赋初始值。
class Box
{
public Box()
{
Console.WriteLine("对象已创建");
}
public double GetLength()
{
return length;
}
static void Main(string[] args)
{
Box box = new Box();
// 设置长度
box.SetLength(6.0);
Console.WriteLine("Box的长度: {0}", box.GetLength());
Console.ReadKey();
}
}
//打印
//对象已创建
//Box的长度: 6
- 类的 析构函数 是类的一个特殊的成员函数,当类的对象超出范围时执行。
- 析构函数的名称是在类的名称前加上一个波浪形(~)作为前缀,它不返回值,也不带任何参数。
- 析构函数用于在结束程序(比如关闭文件、释放内存等)之前释放资源。析构函数不能继承或重载。
class Box
{
public Box()//构造函数
{
Console.WriteLine("对象已创建");
}
~Box()//析构函数
{
Console.WriteLine("对象已删除");
}
public double GetLength()
{
return length;
}
static void Main(string[] args)
{
Box box = new Box();
// 设置长度
box.SetLength(6.0);
Console.WriteLine("Box的长度: {0}", box.GetLength());
Console.ReadKey();
}
}
//打印
//对象已创建
//Box的长度: 6
//对象已删除
我们可以使用 static 关键字把类成员定义为静态的。当我们声明一个类成员为静态时,意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。
关键字 static 意味着类中只有一个该成员的实例。静态变量用于定义常量,因为它们的值可以通过直接调用类而不需要创建类的实例来获取。静态变量可在成员函数或类的定义外部进行初始化。你也可以在类的定义内部初始化静态变量。
class StaticVar
{
public static int num;
public void count()
{
num++;
}
public int getNum()
{
return num;
}
}
class StaticTester
{
static void Main(string[] args)
{
StaticVar s1 = new StaticVar();
StaticVar s2 = new StaticVar();
s1.count();
s1.count();
s1.count();
s2.count();
s2.count();
s2.count();
Console.WriteLine("s1 的变量 num: {0}", s1.getNum());
Console.WriteLine("s2 的变量 num: {0}", s2.getNum());
Console.ReadKey();
}
}
//s1 的变量 num: 6
//s2 的变量 num: 6
- 密封类的定义
如果我们不希望自己编写的类被继承;如果有的类已经没有再被继承的必要,这时,我们可以使用sealed修饰符在类中进行声明,以达到该类不能派生其它类的目的,该类就被称为密封类。- 密封类与抽象类
密封类不能同时又是抽象类,因为密封类不能用作基类、也不能被继承,而抽象类总是希望被继承的。- 什么情况下使用密封类
需要阻止其它程序员无意中继承该类的时候;在程序运行时需要起到优化效果的时候,可以使用密封类。
/* 密封类*/
namespace Test
{
class Program
{
sealed class A // 密封类。不能从该类再派生出其它类
{
public int x;
public int y;
}
static void Main(string[] args)
{
// C#密封类-www.baike369.com
A a = new A();
a.x = 10;
a.y = 20;
Console.WriteLine("x = {0}; y = {1}", a.x, a.y);
Console.ReadLine();
}
}
}
/* 密封方法 */
class A
{
public virtual void F()
{
Console.WriteLine("A.F");
}
public virtual void G()
{
Console.WriteLine("A.G");
}
}
class B : A
{
//密封方法
sealed override public void F()
{
Console.WriteLine("B.F");
}
override public void G()
{
Console.WriteLine("B.G");
}
}
class C : B
{
override public void G()
{
Console.WriteLine("C.G");
}
}
静态类的特性:
- 仅包含静态成员。
- 无法实例化。(不能用new生成对象)
- 是密封的。(不能被继承)
- 不能包含实例构造函数。
public static class StaticClass
{
private static int num;
static StaticClass()
{
//静态构造函数,在使用静态字段、方法或属性时先且仅执行一次。
}
public static void Func()
{
}
public static int Num
{
get { return num; }
}
}
静态成员
- 非静态类可以包含静态的方法、字段、属性或事件;
- 无论对一个类创建多少个实例,它的静态成员都只有一个副本;
- 静态方法和属性不能访问其包含类型中的非静态字段和事件,并且不能访问任何对象的实例变量;
- 静态方法只能被重载,而不能被重写,因为静态方法不属于类的实例成员;
- 虽然字段不能声明为 static const,但 const 字段的行为在本质上是静态的。这样的字段属于类,不属于类的实例。因此,可以同对待静态字段一样使用 ClassName.MemberName 表示法来访问 const 字段;
- C# 不支持静态局部变量(在方法内部定义静态变量)。
静态构造函数
- 静态类可以有静态构造函数,静态构造函数不可继承;
- 静态构造函数可以用于静态类,也可用于非静态类;
- 静态构造函数无访问修饰符、无参数,只有一个 static 标志;
- 静态构造函数不可被直接调用,当创建类实例或引用任何静态成员之前,静态构造函数被自动执行,并且只执行一次。
注意
- 静态类在内存中是一直有位置的;
- 非静态类在实例化后是在内存中是独立的,它的变量不会重复,在使用后会及时销毁,所以不会出现未知的错误。
- 建议更多地使用一般类(非静态类)。
抽象类特征:
- 抽象类不能实例化。
- 抽象类可以包含抽象方法和抽象访问器。
- 不能用 sealed 修饰符修改抽象类,这意味着该类不能被继承。
- 从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实实现。
public abstract class Test
{
private int num;
public int Num
{
get { return num; }
}
private Test()
{
}
public void Func()
{
}
public abstract void Function();
}
抽象类关键字(abstract)
- abstract 修饰符可以和类、方法、属性、索引器及事件一起使用。
- 在类声明中使用 abstract 修饰符以指示类只能是其他类的基类。
抽象方法特点
- 抽象方法是隐式的虚方法
- 抽象方法只允许声明在抽象类中
- 抽象方法不能提供实际的实现,所以没有方法体;抽象方法的实现是在非抽象的派生类中以override重写实现的
public abstract void MyMethod();- 抽象方法声明中不可以使用static或者virtual修饰符
- abstract关键字不能修饰静态方法或静态属性
抽象类与普通类的区别
- 抽象类不能直接实例化,并且对抽象类使用 new 运算符是编译时错误。
- 虽然一些变量和值在编译时的类型可以是抽象的,但是这样的变量和值必须或者为 null,或者含有对非抽象类的实例的引用(此非抽象类是从抽象类派生的)。
- 允许(但不要求)抽象类包含抽象成员。
- 抽象类不能被密封。
抽象类与接口的区别
- 它们的派生类只能继承一个基类(所谓的单继承,多接口继承),即:只能直接继承一个抽象类,但是可以继承任意多个接口。
- 抽象类中可以定义成员的具体实现,但是接口却不行。
- 抽象类中可以包含字段,构造函数,析构函数,静态成员等,接口中不可以。
- 抽象类中的成员可以是私有的(只要它们不是抽象的),受保护的,内部的或者受保护的内部成员,但是接口中的成员必须是公共的(默认就是公共的)。
为了定义一个结构体,您必须使用 struct 语句。struct 语句为程序定义了一个带有多个成员的新的数据类型。
using System;
using System.Text;
struct Books
{
public string title;
public string author;
public string subject;
public int book_id;
};
public class testStructure
{
public static void Main(string[] args)
{
Books Book1; /* 声明 Book1,类型为 Books */
Books Book2; /* 声明 Book2,类型为 Books */
/* book 1 详述 */
Book1.title = "C Programming";
Book1.author = "Nuha Ali";
Book1.subject = "C Programming Tutorial";
Book1.book_id = 6495407;
/* book 2 详述 */
Book2.title = "Telecom Billing";
Book2.author = "Zara Ali";
Book2.subject = "Telecom Billing Tutorial";
Book2.book_id = 6495700;
/* 打印 Book1 信息 */
Console.WriteLine( "Book 1 title : {0}", Book1.title);
Console.WriteLine("Book 1 author : {0}", Book1.author);
Console.WriteLine("Book 1 subject : {0}", Book1.subject);
Console.WriteLine("Book 1 book_id :{0}", Book1.book_id);
/* 打印 Book2 信息 */
Console.WriteLine("Book 2 title : {0}", Book2.title);
Console.WriteLine("Book 2 author : {0}", Book2.author);
Console.WriteLine("Book 2 subject : {0}", Book2.subject);
Console.WriteLine("Book 2 book_id : {0}", Book2.book_id);
Console.ReadKey();
}
}
/*
Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700
*/
using System;
using System.Text;
struct Books
{
private string title;
private string author;
private string subject;
private int book_id;
public void setValues(string t, string a, string s, int id)
{
title = t;
author = a;
subject = s;
book_id =id;
}
public void display()
{
Console.WriteLine("Title : {0}", title);
Console.WriteLine("Author : {0}", author);
Console.WriteLine("Subject : {0}", subject);
Console.WriteLine("Book_id :{0}", book_id);
}
};
public class testStructure
{
public static void Main(string[] args)
{
Books Book1 = new Books(); /* 声明 Book1,类型为 Books */
Books Book2 = new Books(); /* 声明 Book2,类型为 Books */
/* book 1 详述 */
Book1.setValues("C Programming", "Nuha Ali", "C Programming Tutorial",6495407);
/* book 2 详述 */
Book2.setValues("Telecom Billing", "Zara Ali", "Telecom Billing Tutorial", 6495700);
/* 打印 Book1 信息 */
Book1.display();
/* 打印 Book2 信息 */
Book2.display();
Console.ReadKey();
}
}
/*
Title : C Programming
Author : Nuha Ali
Subject : C Programming Tutorial
Book_id : 6495407
Title : Telecom Billing
Author : Zara Ali
Subject : Telecom Billing Tutorial
Book_id : 6495700
*/
接口使用 interface 关键字声明,它与类的声明类似。接口声明默认是 public 的。下面是一个接口声明的实例:
interface IMyInterface
{
void MethodToImplement();
}
以下实例定义了两个接口 IMyInterface 和 IParentInterface。
如果一个接口继承其他接口,那么实现类或结构就需要实现所有接口的成员。
以下实例 IMyInterface 继承了 IParentInterface 接口,因此接口实现类必须实现 MethodToImplement() 和 ParentInterfaceMethod() 方法:
using System;interface IParentInterface
{
void ParentInterfaceMethod();
}
interface IMyInterface : IParentInterface
{
void MethodToImplement();
}
class InterfaceImplementer : IMyInterface
{
static void Main()
{
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.MethodToImplement();
iImp.ParentInterfaceMethod();
}
public void MethodToImplement()
{
Console.WriteLine("MethodToImplement() called.");
}
public void ParentInterfaceMethod()
{
Console.WriteLine("ParentInterfaceMethod() called.");
}
}
当定义一个方法时,从根本上说是在声明它的结构的元素。在 C# 中,定义方法的语法如下:
<Access Specifier> <Return Type> <Method Name>(Parameter List)
{
Method Body
}
访问修饰符 返回类型 方法名称 (参数列表)
{
方法主体
}
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public int FindMax(int num1, int num2)
{
/* 局部变量声明 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
}
class Test
{
static void Main(string[] args)
{
/* 局部变量定义 */
int a = 100;
int b = 200;
int ret;
NumberManipulator n = new NumberManipulator();
//调用 FindMax 方法
ret = n.FindMax(a, b);
Console.WriteLine("最大值是: {0}", ret );
Console.ReadLine();
}
}
}
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public int factorial(int num)
{
/* 局部变量定义 */
int result;
if (num == 1)
{
return 1;
}
else
{
result = factorial(num - 1) * num;
return result;
}
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
//调用 factorial 方法
Console.WriteLine("6 的阶乘是: {0}", n.factorial(6));
Console.WriteLine("7 的阶乘是: {0}", n.factorial(7));
Console.WriteLine("8 的阶乘是: {0}", n.factorial(8));
Console.ReadLine();
}
}
}
/*
6 的阶乘是: 720
7 的阶乘是: 5040
8 的阶乘是: 40320
*/
引用传参 ref
引用参数是一个对变量的内存位置的引用。当按引用传递参数时,与值参数不同的是,它不会为这些参数创建一个新的存储位置。引用参数表示与提供给方法的实际参数具有相同的内存位置。
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void swap(ref int x, ref int y)
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
int b = 200;
Console.WriteLine("在交换之前,a 的值: {0}", a);
Console.WriteLine("在交换之前,b 的值: {0}", b);
/* 调用函数来交换值 */
n.swap(ref a, ref b);
Console.WriteLine("在交换之后,a 的值: {0}", a);
Console.WriteLine("在交换之后,b 的值: {0}", b);
Console.ReadLine();
}
}
}
/*
在交换之前,a 的值:100
在交换之前,b 的值:200
在交换之后,a 的值:200
在交换之后,b 的值:100
*/
输出传参 out
return 语句可用于只从函数中返回一个值。但是,可以使用 输出参数 来从函数中返回两个值。输出参数会把方法输出的数据赋给自己,其他方面与引用参数相似。
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void getValue(out int x )
{
int temp = 5;
x = temp;
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
Console.WriteLine("在方法调用之前,a 的值: {0}", a);
/* 调用函数来获取值 */
n.getValue(out a);
Console.WriteLine("在方法调用之后,a 的值: {0}", a);
Console.ReadLine();
}
}
}
/*
在方法调用之前,a 的值: 100
在方法调用之后,a 的值: 5
*/
ref 和 out 的区别
- ref 型传递变量前,变量必须初始化,否则编译器会报错, 而 out 型则不需要初始化
- ref 型传递变量,数值可以传入方法中,而 out 型无法将数据传入方法中。换而言之,ref 型有进有出,out 型只出不进
- out型数据在方法中必须要赋值,否则编译器会报错
public class MyClass
{
public static void UseParams(params int[] list)
{
for (int i = 0; i < list.Length; i++)
{
Console.Write(list[i] + " ");
}
Console.WriteLine();
}
public static void UseParams2(params object[] list)
{
for (int i = 0; i < list.Length; i++)
{
Console.Write(list[i] + " ");
}
Console.WriteLine();
}
static void Main()
{
UseParams(1, 2, 3, 4);
UseParams2(1, 'a', "test");
UseParams2();
int[] myIntArray = { 5, 6, 7, 8, 9 };
UseParams(myIntArray);
object[] myObjArray = { 2, 'b', "test", "again" };
UseParams2(myObjArray);
UseParams2(myIntArray);
}
}