C# 可空类型
? 单问号用于对 int、double、bool 等无法直接赋值为 null 的数据类型进行 null 的赋值,意思是这个数据类型是 Nullable 类型的。
int i; //默认值0
int? ii; //默认值null
??双问号,为Null合并运算符 用于判断一个变量在为 null 的时候返回一个指定的值。专门用来处理 null
值的! 如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值。
下面举例说明:
double? num1 = null;
double? num2 = 3.14157;
double num3;
num3 = num1 ?? 5.34; // num1 如果为空值则返回 5.34
Console.WriteLine("num3 的值: {0}", num3);
num3 = num2 ?? 5.34;
Console.WriteLine("num3 的值: {0}", num3);
Console.ReadLine();
输出结果为:
num3 的值: 5.34
num3 的值: 3.14157
纳尼,功能怎么和 ? : 三元运算符 这么类似,它跟? : 三元运算符 有什么区别呢,其实它们在功能上不等价,??
是 ?:
在 null
检查这个特定场景下的一个高度优化的、更具表达力的子集。
核心区别总结
特性 | C# ?? (空合并) | C++ / C# ?: (三元条件) |
---|---|---|
核心目的 | 提供一个处理 null 值的默认值。 | 基于一个布尔条件在两个值之间选择。 |
左侧操作数 | 必须是一个可为 null 的类型。 | 必须是一个能得出 true 或 false 的表达式。 |
判断依据 | 值是否为 null | 条件是 true 还是 false |
可读作 | “如果为空,则...” | “如果为真,则...否则...” |
C# 类的静态成员(static)
我们可以使用 static 关键字把类成员定义为静态的。当我们声明一个类成员为静态时,意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。
在C#中,当我们在成员变量(字段)或成员函数(方法)前加上static关键字时,意味着这些成员是静态的,它们属于类本身而不是类的实例。静态变量可在成员函数或类的定义外部进行初始化,也可以在类的定义内部初始化静态变量。以下是static关键字的主要作用:
- 存储位置:静态成员变量存储在堆的静态存储区中,而不是在栈上。它们在类的所有实例之间是共享的。
- 访问方式:静态成员可以通过类名直接访问,无需创建类的实例。
- 生命周期:静态成员变量的生命周期与程序的生命周期相同,从程序启动到程序结束。
- 函数调用:静态方法只能访问静态成员(包括字段、属性、方法、事件等),而不能访问类的实例成员。
- 继承与多态:静态成员不能被继承,因此它们不能被重写(override)或隐藏(new)。
- 用途:静态成员常用于实现工具类方法(如数学计算、字符串处理等)和单例模式等。
两句话总结:
- 类的所有对象共用一个静态成员
- 可以直接调用类获取
C# 多重继承
多重继承指的是一个类别可以同时从多于一个父类继承行为与特征的功能。与单一继承相对,单一继承指一个类别只可以继承自一个父类。C# 不支持类的多重继承,但一个类可以继承多个接口。
C# 多态性
在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object。
- 静态多态性通过 函数重载 和 运算符重载 实现。
- 动态多态性通过 抽象类 和 虚方法 实现的。
关键字 abstract 创建抽象类,相当于 c++里面的纯虚函数,对比
class Shape {
public:
virtual int area() = 0; // 纯虚函数,强制子类实现此方法
};
class Rectangle : public Shape {
private:
int width, height;
public:
Rectangle(int w, int h) : width(w), height(h) { }
int area() override { // 实现纯虚函数
return width * height;
}
};
abstract class Shape //抽象类不能被实例化
{
abstract public int area(); //只声明,不定义函数体,子类必须重写!
}
class Rectangle: Shape
{
private int length;
private int width;
public Rectangle( int a=0, int b=0)
{
length = a;
width = b;
}
public override int area ()
{
Console.WriteLine("Rectangle 类的面积:");
return (width * length);
}
}
虚函数对比
using System;
using System.Collections.Generic;
public class Shape
{
public int X { get; private set; }
public int Y { get; private set; }
public int Height { get; set; }
public int Width { get; set; }
// 虚方法
public virtual void Draw()
{
Console.WriteLine("执行基类的画图任务");
}
}
class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("画一个圆形");
base.Draw();
}
}
class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("画一个长方形");
base.Draw();
}
}
class Triangle : Shape
{
public override void Draw()
{
Console.WriteLine("画一个三角形");
base.Draw();
}
}
class Program
{
static void Main(string[] args)
{
// 创建一个 List<Shape> 对象,并向该对象添加 Circle、Triangle 和 Rectangle
var shapes = new List<Shape>
{
new Rectangle(),
new Triangle(),
new Circle()
};
// 使用 foreach 循环对该列表的派生类进行循环访问,并对其中的每个 Shape 对象调用 Draw 方法
foreach (var shape in shapes)
{
shape.Draw();
}
Console.WriteLine("按下任意键退出。");
Console.ReadKey();
}
}
C#\C++运算符重载对比
c#

c++
C# using
C# 特性(Attribute)
特征的含义
特性(Attribute) 是一种用来添加元数据的机制。它允许在代码元素(如类、方法、属性、字段、参数等)上附加额外的信息或修饰信息,这些信息在编译时或运行时可以通过反射(Reflection)访问和读取。注意:用到特性必须用反射。特性主要分为预定义特性和自定义特性。
特性的主要作用
- 提供元数据信息:用来标志或描述程序中的元素,例如标记哪些方法是测试方法,哪些类需要特殊处理等。
- 支持框架和工具的功能:许多框架(如ASP.NET、Entity Framework等)利用特性实现自动化配置和扩展。
- 控制行为:通过自定义特性,可以影响程序的行为或提供额外信息。
预定义特性
.Net 框架提供了三种预定义特性:
- AttributeUsage 预定义特性 AttributeUsage 描述了如何使用一个自定义特性类。它规定了特性可应用到的项目的类型。
- Conditional 这个预定义特性标记了一个条件方法,其执行依赖于指定的预处理标识符
- Obsolete 这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。
自定义特性
using System; //引入命名空间:
[AttributeUsage(AttributeTargets.All)] //定义自定义特性:
public class HelpAttribute : System.Attribute
{
public readonly string Url;
public string Topic // Topic 是一个命名(named)参数
{
get
{
return topic;
}
set
{
topic = value;
}
}
public HelpAttribute(string url) // url 是一个定位(positional)参数
{
this.Url = url;
}
private string topic;
}
//这段代码定义了一个名为HelpAttribute的自定义属性类,该类继承自System.Attribute。[AttributeUsage(AttributeTargets.All)]表示该属性可以应用于任何类型的程序元素(如类、方法、属性等)。HelpAttribute有两个成员:一个只读的Url属性,以及一个可读写的Topic属性。Url是一个定位参数,意味着它在使用时必须在属性构造函数中指定;而Topic是一个命名参数,可以在创建属性实例时通过名称来设置。
[HelpAttribute("Information on the class MyClass")] //应用自定义特性:
class MyClass
{
}
//这里定义了一个名为MyClass的类,并在类上应用了之前定义的HelpAttribute。属性的Url被设置为“Information on the class MyClass”。
//使用反射获取自定义属性:
namespace AttributeAppl
{
class Program
{
static void Main(string[] args)
{
System.Reflection.MemberInfo info = typeof(MyClass);
object[] attributes = info.GetCustomAttributes(true);
for (int i = 0; i < attributes.Length; i++)
{
System.Console.WriteLine(attributes[i]);
}
Console.ReadKey();
}
}
}
//在这个命名空间AttributeAppl中定义了一个Program类,其Main方法展示了如何使用反射机制来获取MyClass上的自定义属性。typeof(MyClass)返回MyClass的类型信息,GetCustomAttributes(true)方法返回应用于MyClass的所有自定义属性对象的数组,true表示包括继承的属性。接着通过循环遍历输出这些属性对象。
C# 反射(reflection)
根据已经创建出来的对象 拿到对象对应类里面的信息
C#中的反射机制是一种在运行时获取类型信息、动态调用对象成员的技术。它允许程序在运行时查询程序集、类型、方法、属性等信息,并可以动态地创建实例、调用方法或访问成员。
C# 属性(Property)
在 C# 中,属性 用于隐藏字段的内部实现细节,同时提供控制数据访问的机制。可以在访问时加入逻辑(如验证、计算等)。属性通常配合get
和set
访问器使用。
例如,有一个名为 Student 的类,带有 age、name 和 code 的私有域。我们不能在类的范围以外直接访问这些域,但是我们可以拥有访问这些私有域的属性。
C#字段、方法、属性区别
属性的基本语法
下面的Name即为Person类的一个属性
public class Person
{
private string name;
public string Name //Name对私有字段name进行封装
{
get { return name; } //get 读取name值
set { name = value; } //set 设置name值
}
}
自动实现的属性 只读属性 只写属性 计算属性 抽象属性(抽象类可拥有抽象属性,这些属性应在派生类中被实现)
C# 索引器
一维索引器的语法如下:
element-type this[int index]
{
// get 访问器
get
{
// 返回 index 指定的值
}
// set 访问器
set
{
// 设置 index 指定的值
}
}
索引器的用途
索引器的行为的声明在某种程度上类似于属性(property)。就像属性(property),您可使用 get 和 set 访问器来定义索引器。但是,属性返回或设置一个特定的数据成员,而索引器返回或设置对象实例的一个特定值。换句话说,它把实例数据分为更小的部分,并索引每个部分,获取或设置每个部分。
定义一个属性(property)包括提供属性名称。索引器定义的时候不带有名称,但带有 this 关键字,它指向对象实例。
C# 委托
委托的类型:自定义委托 内置委托类型
1、Action
Action:代表不返回值的方法。可以接受最多 16 个参数。
2、Func
Func:代表有返回值的方法。最多接受 16 个参数,第一个参数是输入参数,最后一个参数是返回值类型。
3、Predicate
Predicate:代表返回 bool 值的方法,通常用于条件判断。
// 自定义委托
public delegate int Operation(int x, int y);
Operation op1 = (a, b) => a * b;
Console.WriteLine(op1(3, 4)); // 12
// Action示例
Action<string> printer = Console.WriteLine;
printer("Hello Action!");
// Func示例
Func<int, int, int> sum = (a, b) => a + b;
Console.WriteLine(sum(10, 20)); // 30
// Predicate示例
Predicate<int> greaterThanTen = x => x > 10;
Console.WriteLine(greaterThanTen(15)); // True
声明、实例化
using System;
delegate int NumberChanger(int n); //声明
namespace DelegateAppl
{
class TestDelegate
{
static int num = 10;
public static int AddNum(int p)
{
num += p;
return num;
}
public static int MultNum(int q)
{
num *= q;
return num;
}
public static int getNum()
{
return num;
}
static void Main(string[] args)
{
// 创建委托实例
NumberChanger nc1 = new NumberChanger(AddNum);
NumberChanger nc2 = new NumberChanger(MultNum);
// 使用委托对象调用方法
nc1(25);
Console.WriteLine("Value of Num: {0}", getNum());
nc2(5);
Console.WriteLine("Value of Num: {0}", getNum());
Console.ReadKey();
}
}
}
委托的多播:
- 委托对象可使用 + 运算符进行合并。
- 一个合并委托调用它所合并的两个委托,只有相同类型的委托可被合并。
- - 运算符可用于从合并的委托中移除组件委托。
- 使用委托的这个有用的特点,您可以创建一个委托被调用时要调用的方法的调用列表,这被称为委托的 多播(multicasting),也叫组播。
- 委托和事件:委托常常与事件(Event)一起使用,事件是一种特殊类型的委托,用于发布和订阅机制。
- 在 C# 中,事件本质上就是一个封装了委托的类型,它用于响应程序中的某些操作。
C# 事件
C# 事件(Event)是一种成员,用于将特定的事件通知发送给订阅者。事件通常用于实现观察者模式,它允许一个对象将状态的变化通知其他对象,而不需要知道这些对象的细节。
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
C# 中使用事件机制实现线程间的通信。
C# 不安全代码
当一个代码块使用 unsafe 修饰符标记时,C# 允许在函数中使用指针变量。不安全代码或非托管代码是指使用了指针变量的代码块。