"类型安全"是什么意思?
类型安全意味着编译器将在编译时验证类型,如果尝试将错误的类型分配给变量,则抛出错误.
一些简单的例子:
// Fails, Trying to put an integer in a string String one = 1; // Also fails. int foo = "bar";
这也适用于方法参数,因为您将显式类型传递给它们:
int AddTwoNumbers(int a, int b) { return a + b; }
如果我尝试使用以下方法调用:
int Sum = AddTwoNumbers(5, "5");
编译器会抛出一个错误,因为我传递一个字符串("5"),它期待一个整数.
在松散类型的语言中,例如javascript,我可以执行以下操作:
function AddTwoNumbers(a, b) { return a + b; }
如果我这样称呼它:
Sum = AddTwoNumbers(5, "5");
Javascript自动将5转换为字符串,并返回"55".这是因为javascript使用+符号进行字符串连接.要使其具有类型感知功能,您需要执行以下操作:
function AddTwoNumbers(a, b) { return Number(a) + Number(b); }
或者,可能:
function AddOnlyTwoNumbers(a, b) { if (isNaN(a) || isNaN(b)) return false; return Number(a) + Number(b); }
如果我这样称呼它:
Sum = AddTwoNumbers(5, " dogs");
Javascript会自动将5转换为字符串,并附加它们,以返回"5只狗".
并非所有的动态语言都像javascript一样宽容(事实上,动态语言并不意味着松散的类型语言(参见Python)),其中一些实际上会在无效类型转换时给出运行时错误.
虽然方便,但它可以让您轻松错过许多错误,并且只能通过测试正在运行的程序来识别.就个人而言,我更喜欢让我的编译器告诉我是否犯了这个错误.
现在,回到C#...
C#支持一种称为协方差的语言功能,这基本上意味着您可以将基类型替换为子类型而不会导致错误,例如:
public class Foo : Bar { }
在这里,我创建了一个新的类(Foo),它是Bar的子类.我现在可以创建一个方法:
void DoSomething(Bar myBar)
并使用Foo或Bar作为参数调用它,两者都可以正常工作而不会导致错误.这是有效的,因为C#知道Bar的任何子类都将实现Bar的接口.
但是,你不能这样做:
void DoSomething(Foo myFoo)
在这种情况下,我无法将Bar传递给此方法,因为编译器不知道Bar实现了Foo的接口.这是因为子类可以(通常会)与父类有很大不同.
当然,现在我已经离开了深层次并且超出了原始问题的范围,但它的所有好东西都要知道:)
不应将类型安全与静态/动态类型或强/弱类型混淆.
类型安全语言是指一个人可以对数据执行的唯一操作是由数据类型宽容的语言.也就是说,如果你的数据类型X
和X
不支持的操作y
,则语言不会允许你来执行y(X)
.
此定义不会在选中此选项时设置规则.它可以在编译时(静态类型)或在运行时(动态类型),通常是通过异常.它可以是两者兼而有之:一些静态类型语言允许您将数据从一种类型转换为另一种类型,并且必须在运行时检查强制转换的有效性(假设您正在尝试Object
转换为Consumer
- 编译器没有了解它是否可接受的方式.
类型安全并不一定意味着强类型 - 一些语言的名字很弱,但仍然可以说是类型安全.以Javascript为例:它的类型系统虽然很弱,但仍然严格定义.它允许自动转换数据(例如,字符串到整数),但是在明确定义的规则中.据我所知,Javascript程序不会以未定义的方式运行,如果你足够聪明(我不是),你应该能够预测在阅读Javascript代码时会发生什么.
类型不安全的编程语言的一个例子是C:读取/写入数组边界之外的数组值具有规范的未定义行为.无法预测会发生什么.C是具有类型系统的语言,但不是类型安全的.
这里的许多答案都将类型安全与静态类型和动态类型混为一谈.动态类型语言(如smalltalk)也可以是类型安全的.
简短的回答:如果没有操作导致未定义的行为,则语言被认为是类型安全的.许多人认为要严格键入语言所需的显式类型转换,因为自动转换有时会导致明确定义但意外/不直观的行为.
类型安全不仅仅是编译时约束,而是运行时约束.我觉得即便这样,我们可以进一步明确这一点.
有两个与类型安全相关的主要问题.内存**和数据类型(及其相应的操作).
A char
通常需要每个字符1个字节,或8位(取决于语言,Java和C#存储unicode字符,需要16位).一个int
需要4个字节,或32位(通常).
视觉:
char: |-|-|-|-|-|-|-|-|
int : |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-|
类型安全语言不允许在运行时将int插入到char中(这应该抛出某种类型的转换或内存不足异常).但是,在类型不安全的语言中,您将覆盖3个相邻内存字节中的现有数据.
int >> char:
|-|-|-|-|-|-|-|-| |?|?|?|?|?|?|?|?| |?|?|?|?|?|?|?|?| |?|?|?|?|?|?|?|?|
在上面的例子中,右边的3个字节被覆盖,因此任何指向该内存的指针(比如3个连续的字符)都会产生垃圾.这会导致undefined
程序中的行为(或者更糟糕的是,可能在其他程序中,这取决于操作系统如何分配内存 - 这些日子不太可能).
**虽然第一个问题在技术上并不是关于数据类型的,但是类型安全语言本身就能解决这个问题,它可以直观地向那些不知道内存分配如何"看起来"的人描述问题.
更微妙和直接的类型问题是两种数据类型使用相同的内存分配.取一个int与一个unsigned int.两者都是32位.(同样容易成为char [4]和int,但更常见的问题是uint与int).
|-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-|
|-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-|
类型不安全的语言允许程序员引用正确分配的32位跨度,但是当unsigned int的值被读入int的空间(反之亦然)时,我们再次有undefined
行为.想象一下这可能会导致银行业的问题:
"伙计!我花了30美元,现在剩下65,506美元!"
......当然,银行业务程序使用更大的数据类型.;) 大声笑!
正如其他人已经指出的那样,下一个问题是对类型的计算操作.这已经被充分涵盖了.
今天大多数程序员从不需要担心这些事情,除非他们使用像C或C++这样的东西.尽管编译器尽最大努力将风险降至最低,但这两种语言都允许程序员在运行时轻松违反类型安全性(直接内存引用).但是,这并非全是坏事.
这些语言计算速度快的一个原因是它们不会在运行时操作(例如Java)中验证类型兼容性.他们认为开发人员是一个很理性的人,他们不会将字符串和int一起添加,为此,开发人员可以获得速度/效率.
"类型安全"的编程语言意味着以下事项:
您无法读取未初始化的变量
您不能将数组索引超出其范围
您无法执行未经检查的类型转换
一个文科专业的解释,而不是一个复杂的科学专业:
当人们说语言或语言特性是类型安全的时,他们意味着该语言将帮助阻止您,例如,将非整数的东西传递给某个需要整数的逻辑.
例如,在C#中,我将函数定义为:
void foo(int arg)
编译器将阻止我这样做:
// call foo foo("hello world")
在其他语言中,编译器不会阻止我(或者没有编译器...),因此字符串将被传递给逻辑,然后可能发生一些不好的事情.
类型安全语言试图在"编译时"捕获更多.
在不利的情况下,使用类型安全的语言,当你有一个像"123"这样的字符串并且你想像int一样操作它时,你必须编写更多的代码来将字符串转换为int,或者你有一个int喜欢123,并希望在消息中使用它,如"答案是123",你必须编写更多的代码来转换/转换为字符串.