信息的编码表示
计算机要处理的信息是多种多样的,如十进制数、文字、符号、图形、音频、视频等,这些信息在人们的眼里是不同的。但对于计算机来说,它们在内存中都是一样的,都是以二进制的形式来表示,说白了就是1001 0011 0000 1011之类的。
前面我们已经讲到,计算机是以二进制的形式来存储数据的,它只认识0和1两个数字,我们在屏幕上看到的文字,在存储到内存之前也都被转换成了二进制(0和1序列)。可想而知,特定的文字必然对应着固定的二进制,否则将无法转换。那么,怎样将文字与二进制对应呢?这就需要有一套规范,计算机公司和软件开发者都必须遵守。
ASCII码
我们知道,一个二进制位(Bit)有0、1两种状态,一个字节(Byte)有8个二进制位,有256种状态,每种状态对应一个符号,就是256个符号,从00000000到11111111。计算机诞生于美国,早期的计算机使用者大多使用英文,上世纪60年代,美国制定了一套英文字符与二进制位的对应关系,称为ASCII码,沿用至今。ASCII码规定了128个英文字符与二进制的对应关系,占用一个字节(实际上只占用了一个字节的后面7位,最前面1位统一规定为0)。例如,字母 a 的的ASCII码为 01100001,那么你暂时可以理解为字母 a 存储到内存之前会被转换为 01100001,读取时遇到 01100001 也会转换为 a。
完整的ASCII码表请查看:http://www.asciima.com/
Unicode编码
随着计算机的流行,使用计算机的人越来越多,不仅限于美国,整个世界都在使用,这个时候ASCII编码的问题就凸现出来了。ASCII编码只占用1个字节,最多只能表示256个字符,我大中华区10万汉字怎么表示,日语韩语拉丁语怎么表示?所以90年代又制定了一套新的规范,将全世界范围内的字符统一使用一种方式在计算机中表示,这就是Unicode编码(Unique Code),也称统一码、万国码。
Unicode 是一个很大的集合,现在的规模可以容纳100多万个符号,每个符号的对应的二进制都不一样。Unicode 规定可以使用多个字节表示一个字符,例如 a 的编码为 01100001,一个字节就够了,”好“的编码为 01011001 01111101,需要两个字节。
为了兼容ASCII,Unicode 规定前0127个字符与ASCII是一样的,不一样的只是128255的这一段。
如果你希望将字符转换为Unicode编码,请查看:http://tool.chinaz.com/Tools/Unicode.aspx
完整的Unicode编码请查看:unicode.org
对Unicode的支持,是检验现代编程语言的标准之一。
数据在内存中的存储
对于现代冯-诺依曼机型的计算机而言,内存就是存储器,是处理器(CPU)存取数据的地方。现代计算机的内存越来越大,基本上可以运行各种程序而不依赖外部存储(硬盘等)。
数据存放在内存中,就要在内存中分出一个个存储单元,写入数据。这就带来几个问题:单元的规格(是一样的还是不一样的)、对这些单元如何管理(寻址、垃圾回收、安全问题等)、如何操作这些单元。在现代计算机科学中,这些内容被归纳为数据结构,与算法并称为编程的两大基本知识。一些大师声称:数据结构比算法更重要。
在数据存储方面,需要考量的是存取的效率、安全性、空间占用和垃圾处理问题。
变量和常量
变量(Variable)是程序操作的基本单位。因为程序中要处理大量数据,这些数据必须起个名字,不然怎么区分呢?我们写:
var a; //这不但命名了一个变量,而且在内存中划分出一个空间用于存储它
a = 10; //在这个空间写入数值10,现在a的值就是10
变量名不仅仅是为数据起了一个好记的名字,还告诉我们数据存储在哪里,使用数据时,只要提供变量名即可。在几乎所有语言中,变量都需要先声明再使用。当我们声明一个变量并赋值给它时,就是打开了一扇大门,把我们的意念注入到计算机之中。
程序中要处理各种各样的数据。现实生活中我们会找一个小箱子来存放物品,一来显得不那么凌乱,二来方便以后找到。计算机也是这个道理,我们需要先在内存中找一块区域,规定用它来存放整数,并起一个好记的名字,方便以后查找。这块区域就是“小箱子”,我们可以把整数放进去了。
C语言中这样在内存中找一块区域:
int a;
int又是一个新单词,它是 Integer 的简写,意思是整数。a 是我们给这块区域起的名字;当然也可以叫其他名字,例如 abc、mn123 等。
这个语句的意思是:在内存中找一块区域,命名为 a,用它来存放整数。
注意 int 和 a 之间是有空格的,它们是两个词。也注意最后的分号,int a表达了完整的意思,是一个语句,要用分号来结束。
不过int a;仅仅是在内存中找了一块可以保存整数的区域,那么如何将 123、100、999 这样的数字放进去呢?
C语言中这样向内存中放整数:
a=123;
=是一个新符号,它在数学中叫“等于号”,例如 1+2=3,但在C语言中,这个过程叫做赋值(Assign)。赋值是指把数据放到内存的过程。
把上面的两个语句连起来:
int a;
a=123;
就把 123 放到了一块叫做 a 的内存区域。你也可以写成一个语句:
int a=123;
a 中的整数不是一成不变的,只要我们需要,随时可以更改。更改的方式就是再次赋值,例如:
int a=123;
a=1000;
a=9999;
第二次赋值,会把第一次的数据覆盖(擦除)掉,也就是说,a 中最后的值是9999,123、1000 已经不存在了,再也找不回来了。
因为 a 的值可以改变,所以我们给它起了一个形象的名字,叫做变量(Variable)。
int a;创造了一个变量 a,我们把这个过程叫做变量定义。a=123;把 123 交给了变量 a,我们把这个过程叫做给变量赋值;又因为是第一次赋值,也称变量的初始化,或者赋初值。
你可以先定义变量,再初始化,例如:
int abc;
abc=999;
也可以在定义的同时进行初始化,例如:
int abc=999;
这两种方式是等价的。
变量的声明和赋值
变量的声明和赋值是所有程序语言的最基本命令。以下是各种语言的变量声明和赋值语法:
C: 数据类型+变量名称
int a;
a = 10;
int a, b, c; //一次声明多个变量
int a = 10; //同时声明和赋值
Java: 与C语言完全相同。
JavaScript: 统一使用var关键字来声明变量,不区分数据类型
var a;
a = 10;
var a = 20; //同时声明和赋值
var name="Gates", age=56, job="CEO"; //一条语句,多个变量
ES6新增了let命令,用来声明变量。它的用法类似于var
,但是所声明的变量,只在let命令所在的代码块内有效。
{ let a = 10; var b = 1;}a // ReferenceError: a is not defined.b // 1
上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。
Swift:类似JavaScript,统一用var 声明变量(语句结尾不用带分号了)
var a = 10
var a = 1, b = 2, c = 3
与JavaScript不同,Swift是强类型的语言,但Swift可以根据赋值自动判断出变量的类型!
当你声明常量或者变量的时候可以加上类型标注(type annotation),说明常量或者变量中要存储的值的类型。如果要添加类型标注,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
这个例子给welcomeMessage变量添加了类型标注,表示这个变量可以存储String类型的值:
var welcomeMessage: String
声明中的冒号代表着“是...类型”,所以这行代码可以被理解为:“声明一个类型为String,名字为welcomeMessage的变量。”“类型为String”的意思是“可以存储任意String类型的值。”
welcomeMessage变量现在可以被设置成任意字符串:
welcomeMessage = "Hello"
你可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型标注:
var red, green, blue: Double
常量的声明和赋值
实践中,有些数值我们不希望它的值变化(否则容易出错),我们可以把它定义为常量(constant), 如C语言中
#define Pi 3.14
就把Pi固定为3.14,如果以后我们不小心对Pi重新赋值,编译器就会报错。这减少了出错的可能。因此,一种编程语言应该区分变量和常量。常量在程序运行过程中主要有2个作用:
- 代表常数,便于程序的修改(例如:圆周率的值);
- 增强程序的可读性(例如:常量UP、DOWN、LEFT和RIGHT分辨代表上下左右,其数值分别是1、2、3和4)。
C语言使用上述预处理命令来定义常量 。这在C语言中称为“符号常量”,意思时它的主要用法在于用一个符号来替代一个值或文本。在程序中所有出现Pi的地方在编译时都会被3.14所代替。
Java中,定义常量只需要在前面添加关键字final即可。在Java编码规范中,要求常量名必须大写。
则常量的语法格式如下:
final 数据类型 常量名称 = 值;
final 数据类型 常量名称1 = 值1, 常量名称2 = 值2,……常量名称n = 值n;
例如:
final double PI = 3.14;
final char MALE=‘M’,FEMALE=‘F’;
在Java语法中,常量也可以首先声明,然后再进行赋值,但是只能赋值一次,示例代码如下:
final int UP;
UP = 1;
JavaScript:
ES6新增了const关键字用来声明常量。一旦声明,常量的值就不能改变。
'use strict';
const PI = 3.1415;
PI = 3;// TypeError: "PI" is read-only
上面代码表明改变常量的值会报错。注意,如果是常规模式,对常量赋值不会报错,但也是无效的。
const PI = 3.1415;
PI = 3; // 常规模式时,重新赋值无效,但不报错
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
Swift:使用let命令声明常量并赋值
let a = 10
变量和常量的命名
变量不仅代表着我们的输入,也代表着程序运行的各种状态。因此,变量的名字非常重要。起名字往往是程序员最伤脑筋的一件事。清晰代表变量用途的名字让程序一目了然,但太长又不好记不好写。
名字并不是语法所规定的,但现代编程者趋向于按照共同的规则:用宁肯较长的有意义的名字,驼峰拼写法;首字母小写;常量大写。
在Swift中,你可以用任何你喜欢的字符作为常量和变量名,包括 Unicode 字符:
let π = 3.14159
let 你好 = "你好世界"
let = "dogcow"
常量与变量名不能包含数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。当然,最好还是使用英文字母作为变量和常量的名字。
综上所述,Swift的var、let表示法最优雅,JavaScript的var、const也还行。
作用域
变量的作用域是指哪个范围内变量的声明有效。一般分为全局变量和局部变量。全局变量在整个源文件中有效,一般在函数外定义,又称为外部变量。 外部变量可加强函数模块之间的数据联系(比如共用的数据),但是又使函数要依赖这些变量,因而使得函数的独立性降低。从模块化程序设计的观点来看这是不利的,
因此在不必要时尽量不要使用全局变量。
局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内, 离开该函数后再使用这种变量是非法的。
C语言明确区分全局变量和局部变量。在函数中声明的都是局部变量,在函数外声明的是全局变量(最好加extern)。但在函数内使用外部变量时要加以说明。
int vs(int l,int w)
{
extern int h; //说明这里引用外部变量
int v;
v=l*w*h;
return v;
}
main()
{
extern int w,h;
int l=5;
printf("v=%d",vs(l,w));
}
int l=3,w=4,h=5;
Java:
大多数其他计算机语言定义了两大类作用域:全局和局部。然而,这些传统型的作用域不适合Java 的严格的面向对象的模型。当然将一个变量定义为全局变量是可行的,但这是例外而不是规则。在Java 中2个主要的作用域是通过类和方法定义的。尽管类的作用域和方法的作用域的区别有点人为划定。因为类的作用域有若干独特的特点和属性,而且这些特点和属性不能应用到方法定义的作用域,这些差别还是很有意义的。因为有差别,类(以及在其内定义的变量)的作用域将被推迟到第6章当讨论类时再来讨论。到现在为止,我们将仅仅考虑由方法或在一个方法内定义的作用域。
方法定义的作用域以它的左大括号开始。但是,如果该方法有参数,那么它们也被包括在该方法的作用域中。可认为它们与方法中其他变量的作用域一样。
作为一个通用规则,在一个作用域中定义的变量对于该作用域外的程序是不可见(即访问)的。因此,当你在一个作用域中定义一个变量时,你就将该变量局部化并且保护它不被非授权访问和/或修改。实际上,作用域规则为封装提供了基础。
作用域可以进行嵌套。例如每次当你创建一个程序块,你就创建了一个新的嵌套的作用域。这样,外面的作用域包含内部的作用域。这意味着外部作用域定义的对象对于内部作用域中的程序是可见的。但是,反过来就是错误的。内部作用域定义的对象对于外部是不可见的。
为理解嵌套作用域的效果,考虑下面的程序:
// Demonstrate block scope.
class Scope {
public static void main(String args[])
{
int x; // known to all code within main
x = 10;
if(x == 10)
{ // start new scope
int y = 20; // known only to this block
// x and y both known here.
System.out.println("x and y: " + x + " " + y);
x = y * 2;
}
// y = 100; // Error! y not known here
// x is still known here.
System.out.println("x is " + x);
}
}
正如注释中说明的那样,在方法main() 的开始定义了变量x,因此它对于main() 中的所有的随后的代码都是可见的。在if程序块中定义了变量y。因为一个块定义一个作用域,y 仅仅对在它的块以内的其他代码可见。这就是在它的块之外的程序行y=100; 被注释掉的原因。如果你将该行前面的注释符号去掉,编译程序时就会出现错误,因为变量y在它的程序块之外是不可见的。在if程序块中可以使用变量x,因为块(即一个嵌套作用域)中的程序可以访问被其包围作用域中定义的变量。
另一个需要记住的重要之处是:变量在其作用域内被创建,离开其作用域时被撤消。
这意味着一个变量一旦离开它的作用域,将不再保存它的值了。因此,在一个方法内定义的变量在几次调用该方法之间将不再保存它们的值。同样,在块内定义的变量在离开该块时也将丢弃它的值。因此,一个变量的生存期就被限定在它的作用域中。
如果一个声明定义包括一个初始化,那么每次进入声明它的程序块时,该变量都要被重新初始化。例如,考虑这个程序:
// Demonstrate lifetime of a variable.
class LifeTime {
public static void main(String args[])
{
int x;
for(x = 0; x < 3; x++)
{
int y = -1; // y is initialized each time block is enter
System.out.println("y is: " + y); // this always prints -1
y = 100;
System.out.println("y is now: " + y);
}
}
}
该程序运行的输出如下:
y is: -1
y is now: 100
y is: -1
y is now: 100
y is: -1
y is now: 100
可以看到,每次进入内部的for循环,y都要被重新初始化为-1。即使它随后被赋值为100,该值还是被丢弃了。
最后一点:尽管程序块能被嵌套,你不能将内部作用域声明的变量与其外部作用域声明的变量重名。在这一点上,Java 不同于C和C++。
JavaScript:
传统的JS语法中最被人诟病的一点就是依赖于全局变量,在声明局部变量是一定要使用var语句,否则会视为对全局变量的引用。看下面代码:
/* 错误修改了全局变量 */
var scope = "global";
function checkScope(){
scope = "local";
document.write(scope);
}
checkScope(); //输出"local"
document.write(scope); //输出"local"
Javascript没有块级作用域,函数中声明的变量在整个函数中都是有定义的。有可能导致内层变量覆盖外层变量:
/* 变量提升 */
var scope = "global";
function checkScope() {
document.write(scope); //语句1
var scope = "local"; //语句2
document.write(scope); //语句3
}
checkScope(); //输出"undefined 、local"
如果没有语句2和3,语句1将输出“global”。由于语句2(var scope = "local";)声明的变量在整个checkScope函数作用域内都有效,因此在语句1(**document.write(scope); **)执行的时scope引用的是局部变量,而此时局部变量scope尚未定义,所以输出”undefined”。因此一个好的编程习惯是将所有的变量声明集中起来放在函数的开头。
ES5只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。第一种场景,内层变量可能会覆盖外层变量。如上面的例子。第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++){
console.log(s[i]);
}
console.log(i); // 5
上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
ES6新增的let命令实际上为JavaScript新增了块级作用域。只要用花括号括起的代码段就是一个块级作用域。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。
for循环的计数器,就很合适使用let命令。
for(let i = 0; i < arr.length; i++){}
console.log(i)//ReferenceError: i is not defined
上面代码的计数器i,只在for循环体内有效。
ES6允许块级作用域的任意嵌套。
内层作用域可以定义外层作用域的同名变量。
{{{{ let insane = 'Hello World';
{let insane = 'Hello World';}
}}}};
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
因此,使用ES6,最好用let声明变量。
数据类型
从C语言开始,为了节约空间和便于管理,把存储数据的单元规范为几种“数据类型”。每种数据类型都有一些相对应的操作。如果一种类型的数据要执行另外类型的操作,比如,将数字连接在一起,就要先进行数据类型转换。数据类型则指明了数据的长度和处理方式。所以诸如int n、char c、float money这样的形式就确定了数据在内存中的所有要素。C语言提供的多种数据类型让程序更加灵活和高效,同时也增加了学习成本。而有些编程语言,例如PHP、JavaScript等,在定义变量时不需要指明数据类型,编译器会根据赋值情况自动推演出数据类型,更加智能。除了C语言,Java、C++、C#等在定义变量时也必须指明数据类型,这样的编程语言称为强类型语言。而PHP、JavaScript等在定义变量时不必指明数据类型,编译系统会自动推演,这样的编程语言称为弱类型语言。强类型语言一旦确定了数据类型,就不能再赋给其他类型的数据,除非对数据类型进行转换。弱类型语言没有这种限制,一个变量,可以先赋给一个整数,然后再赋给一个字符串。最后需要说明的是:数据类型只在定义变量时指明,而且必须指明;使用变量时无需再指明,因为此时的数据类型已经确定了。
那么,到底强类型好还是弱类型好呢?强类型语言安全性高,程序执行效率高,节省内存空间,但编码是需指明类型和进行转换。弱类型编码方便,但需要编译器判断,执行效率低些,内存占用多。从“尽量让机器干活”的角度,还是弱类型好些。
C语言的基本数据类型###
** 整数
计算机最基本的工作是处理数字和数学计算。整数的处理是其中最基本的。从c时代起,整数的表示和处理就有一套规范。
C语言中有三种整数类型,分别为 short、int 和 long。int 称为整型,short 称为短整型,long 称为长整型,它们的长度(所占字节数)关系为:short <= int <= long它们具体占用几个字节C语言并没有规定,C语言只做了宽泛的限制:short 至少占用2个字节。int 建议为一个机器字长。32位环境下机器字长为4字节,64位环境下机器字长为8字节。short 的长度不能大于 int,long 的长度不能小于 int。这就意味着,short 并不一定真的”短“,long 也并不一定真的”长“,它们有可能和 int 占用相同的字节数。决定整数长度的因素很多,包括硬件(CPU和数据总线)、操作系统、编译器等。在16位环境下,short 为2个字节,int 为2个字节,long 为4个字节。16位环境多用于单片机和低级嵌入式系统,在PC和服务器上基本都看不到了。对于32位的 Windows、Linux 和 OS X,short 为2个字节,int 为4个字节,long 也为4个字节。PC和服务器上的32位系统占有率也在慢慢下降,嵌入式系统使用32位越来越多。在64位环境下,不同的操作系统会有不同的结果,如下所示(长度以字节计):
操作系统 short int long
Win64 2 4 4
类Unix系统(包括 Unix、Linux、OS X、BSD、Solaris 等) 2 4 8
获取某个数据类型的长度可以使用 sizeof 操作符
符号位
在数学中,数字有正负之分。在C语言中也是一样,short、int、long 都可以带上符号,例如:
short a = -10; //负数
int b = +10; //正数
long c = (-9) + (+12); //负数和正数相加
如果不带正负号,默认就是正数。
在符号位中,用0表示正数,用1表示负数。例如 short 类型的 -10、+16 在内存中的表示如下:
如果不希望设置符号位,可以在数据类型前面加 unsigned,如下所示:unsigned short a = 12;
unsigned int b = 1002;
unsigned long c = 9892320;
这样,short、int、long 中就没有符号位了,所有的位都用来表示数值。也就意味着,使用了 unsigned 只能表示正数,不能表示负数了。
取值范围和数据溢出
short、int、long 占用的字节数不同,所能表示的数值范围也不同。以32位平台为例,下面是它们的取值范围:
数据类型 | 所占字节 | 取值范围 |
---|---|---|
short | 2 | -32768~32767 |
unsigned short | 2 | 0~65535 |
int | 4 | -2147483648~2147483647(±21亿) |
unsigned int | 4 | 0~4294967295(42亿) |
long | 4 | -2147483648~2147483647 |
unsigned long | 4 | 0~4294967295 |
当数值过大或过小时,有限的几个字节就不能表示,就会发生溢出。发生溢出时,最高位会被截去。请看下面的例子:
#include <stdio.h>
int main()
{
unsigned int a = 0x100000000;
printf("a=%u\n", a);
return 0;
}
运行结果:a=0变量 a 为 int 类型,占用4个字节(32位),能表示的最大值为 0xFFFFFFFF,而 0x100000000 = 0xFFFFFFFF + 1,占用33位,已超出 a 所能表示的最大值,会发生溢出,最高位被截去,剩下的32位都是0。也就是说,在 a 被输出前,其值已经变成了 0。
各种整数的输出
在使用 printf 输出整数时,不同的控制字符会有不同的输出格式。1) 输出 int 使用%d,输出 short 使用 %hd,输出 long 使用 %ld。2) 输出无符号数使用%u。3) 输出十进制使用%d,输出八进制使用%o,输出十六进制使用%x或%X。如果希望带上前缀,可以加#,例如 %#d、%#o、%#x、%#X。
小数
在计算机中表示小数是一件很麻烦的事,这是二进制本身的特点决定的。C语言证明了这一点。
C语言中小数(又称为浮点数)的数据类型为 float 或 double:float 称为单精度浮点数,double 称为双精度浮点数。不像整数,小数的长度始终是固定的,float 占用4个字节,double 占用8个字节。
C语言在内部采用指数形式表示小数。正负号、指数(n)、尾数(a) 是变化的,需要占用内存空间来表示。float、double 在内存中的形式如下所示:
输出 float 使用 %f 控制符,输出 double 使用 %lf 控制符。
#include <stdio.h>
#include <stdlib.h>
int main()
{
float a=128.101;
float b=0.302f;
float c=1.23002398f;
double d=123;
double e = 78.429;
printf("a=%f \nb=%f \nc=%f \nd=%lf \ne=%lf\n", a, b, c, d, e);
system("pause");
return 0;
}
对代码的说明:1) %f 默认保留六位小数,不足六位以 0 补齐,超过六位按四舍五入截断。2) 将整数赋值给 float 变量时会转换为小数。3) 小数默认为 double 类型,加上后缀f才是float类型。4) 由于内存有限,小数的精度受限,所以输出 a 时只能获得一个近似数。
字符和字符串
在人机交互的方式中,自然语言无疑是最自然的方式,而通过文本交互又是使用自然语言与机器对话的最基本形式,这就涉及字符串的处理。因此,对任何编程语言,对字符串的处理是非常重要的。
恰恰,字符串在机器中的表示和处理又是非常复杂的一件事。人类语言使用了多种多样的字符(包括标点符号),字符串本身的长度又不固定,字符串的操作又很多样,所以对编程语言来说,处理好字符串是很考验功力的。
在C语言中,根本就没有字符串这种数据类型。C语言中只有字符(char)这种数据类型,占用一个字节,可以存放本地字符集(一般就是ASCII字符集)中的一个字符,表示为char c = 'a'。char 变量在内存中存储的是字符对应的 ASCII 码值。如果以 %c 输出,会根据 ASCII 码表转换成对应的字符;如果以 %d 输出,那么还是整数。C语言还定义了一些转义字符,如,用\n表示换行符,用\r表示回车符等。
C语言标准库提供的输入输出模型非常简单,都是按照字符流的方式处理,每次读/写一个字符,如getchar和putchar函数。C语言将字符串看做字符数组,用数组的线性存储模式存储字符串,除了字符,最后还要存一个\n。为了用变量表示字符串,可以用数组的指针。可以借助下面的形式将字符串赋值给变量:
char variableName = "string";
char和是固定的形式,variableNmae 为变量名称,"string" 是要赋值的字符串。
字符串使用示例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char c = '@';
char *str = "This is a string.";
printf("char: %c\n", c);
printf("string1: %s\n", str);
//也可以直接输出字符串
printf("string2: %s\n", "This is another string.");
system("pause");
return 0;
}
运行结果:char: @string1: This is a string.string2: This is another string.
C89标准中没有定义布尔类型;C99中增加了_Bool类型。
_Bool型变量实际上只是一个整数类型,
但是,_Bool只能被赋值为0或1,凡是不为0的整数都被转变为1。
C语言中还有一些复合类型,如数组、结构、联合等,不作为基本数据类型在这里描述。
JAVA的基本数据类型###
Java的基本数据类型就是内置数据类型(除此之外还有引用数据类型)。
Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。
byte:
byte数据类型是8位、有符号的,以二进制补码表示的整数;
最小值是-128(-2^7);
最大值是127(2^7-1);
默认值是0;
byte类型用在大型数组中节约空间,主要代替整数,因为byte变量占用的空间只有int类型的四分之一;
例子:byte a = 100,byte b = -50。short:
short数据类型是16位、有符号的以二进制补码表示的整数
最小值是-32768(-2^15);
最大值是32767(2^15 - 1);
Short数据类型也可以像byte那样节省空间。一个short变量是int型变量所占空间的二分之一;
默认值是0;
例子:short s = 1000,short r = -20000。int:
int数据类型是32位、有符号的以二进制补码表示的整数;
最小值是-2,147,483,648(-2^31);
最大值是2,147,485,647(2^31 - 1);
一般地整型变量默认为int类型;
默认值是0;
例子:int a = 100000, int b = -200000。long:
long数据类型是64位、有符号的以二进制补码表示的整数;
最小值是-9,223,372,036,854,775,808(-2^63);
最大值是9,223,372,036,854,775,807(2^63 -1);
这种类型主要使用在需要比较大整数的系统上;
默认值是0L;
例子: long a = 100000L,Long b = -200000L。float:
float数据类型是单精度、32位、符合IEEE 754标准的浮点数;
float在储存大型浮点数组的时候可节省内存空间;
默认值是0.0f;
浮点数不能用来表示精确的值,如货币;
例子:float f1 = 234.5f。
最小值:Float.MIN_VALUE=1.4E-45
最大值:Float.MAX_VALUE=3.4028235E38double:
double数据类型是双精度、64位、符合IEEE 754标准的浮点数;
浮点数的默认类型为double类型;
double类型同样不能表示精确的值,如货币;
默认值是0.0d;
例子:double d1 = 123.4。
最小值:Double.MIN_VALUE=4.9E-324
最大值:Double.MAX_VALUE=1.7976931348623157E308
Float和Double的最小值和最大值都是以科学记数法的形式输出的,结尾的"E+数字"表示E之前的数字要乘以10的多少倍。比如3.14E3就是3.14×1000=3140,3.14E-3就是3.14/1000=0.00314。boolean:Java中引入了布尔类型。
boolean数据类型表示一位的信息;
只有两个取值:true和false;
这种类型只作为一种标志来记录true/false情况;
默认值是false;
例子:boolean one = true。char:
char类型是一个单一的16位Unicode字符;
最小值是’\u0000’(即为0);
最大值是’\uffff’(即为65,535);
char数据类型可以储存任何字符;
例子:char letter = ‘A’。
在Java中字符串属于对象,Java提供了String类来创建和操作字符串。
总的来讲,Java的数据类型比较齐整严谨,支持的数值范围也足够大。
实际上,JAVA中还存在另外一种基本类型void,它也有对应的包装类 java.lang.Void,不过我们无法直接对它们进行操作。
JavaScript的数据类型###
作为弱类型语言的代表,JavaScript不强调变量的“类型”,实际上,你可以随时给变量赋予任何类型:无论数字、字母还是字符串、布尔值。
var x // x 为 undefined
x = 6; // x 为数字
x = "Bill"; // x 为字符串
x = 34.00; //小数
x =123e-5; // 0.00123
x = true
事实上,JavaScript是使用了对象表示一切: Number 对象表示数字。String(字符串)对象表示字符和字符串。每种对象有不同的方法,所以有时还要用Number()、 toString()等方法转换。
数字对象
JavaScript 只有一种数字类型。
可以使用也可以不使用小数点来书写数字。
实例
var pi=3.14; // 使用小数点var x=34; // 不使用小数点
极大或极小的数字可通过科学(指数)计数法来写:
实例
var y=123e5; // 12300000var z=123e-5; // 0.00123
所有 JavaScript 数字均为 64 位
JavaScript 不是类型语言。与许多其他编程语言不同,JavaScript 不定义不同类型的数字,比如整数、短、长、浮点等等。
JavaScript 中的所有数字都存储为根为 10 的 64 位(8 比特),浮点数。
精度
整数(不使用小数点或指数计数法)最多为 15 位(十兆级)。
小数的最大位数是 17,但是浮点运算并不总是 100% 准确:
实例
0.2 + 0.1 = 0.30000000000000004
八进制和十六进制
如果前缀为 0,则 JavaScript 会把数值常量解释为八进制数,如果前缀为 0 和 "x",则解释为十六进制数。
实例
var y=0377;var z=0xFF;
提示:绝不要在数字前面写零,除非您需要进行八进制转换。
数字属性和方法
属性:
MAX VALUE
MIN VALUE
NEGATIVE INFINITIVE
POSITIVE INFINITIVE
NaN
prototype
constructor
方法:
toExponential()
toFixed()
toPrecision()
toString()
valueOf()
字符串(String) 对象描述
字符串是 JavaScript 的一种基本的数据类型。
String 对象的 length 属性声明了该字符串中的字符数。
String 类定义了大量操作字符串的方法,例如从字符串中提取字符或子串,或者检索字符或子串。
需要注意的是,JavaScript 的字符串是不可变的(immutable),String 类定义的方法都不能改变字符串的内容。像 String.toUpperCase() 这样的方法,返回的是全新的字符串,而不是修改原始字符串。
在较早的 Netscape 代码基的 JavaScript 实现中(例如 Firefox 实现中),字符串的行为就像只读的字符数组。例如,从字符串 s 中提取第三个字符,可以用 s[2] 代替更加标准的 s.charAt(2)。此外,对字符串应用 for/in 循环时,它将枚举字符串中每个字符的数组下标(但要注意,ECMAScript 标准规定,不能枚举 length 属性)。因为字符串的数组行为不标准,所以应该避免使用它。
ES6加强了对Unicode的支持,并且扩展了字符串对象。
ES6提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。
0b111110111 === 503 // true
0o767 === 503 // true
ES6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法,用来检查Infinite和NaN这两个特殊值。
Number.EPSILON
ES6在Number对象上面,新增一个极小的常量Number.EPSILON
。
Number.EPSILON// 2.220446049250313e-16Number.EPSILON.toFixed(20)// '0.00000000000000022204'
引入一个这么小的量的目的,在于为浮点数计算,设置一个误差范围。我们知道浮点数计算是不精确的。
0.1 + 0.2// 0.300000000000000040.1 + 0.2 - 0.3// 5.551115123125783e-175.551115123125783e-17.toFixed(20)// '0.00000000000000005551'
但是如果这个误差能够小于Number.EPSILON
,我们就可以认为得到了正确结果。
5.551115123125783e-17 < Number.EPSILON// true
因此,Number.EPSILON
的实质是一个可以接受的误差范围。
function withinErrorMargin (left, right) { return Math.abs(left - right) < Number.EPSILON}withinErrorMargin(0.1 + 0.2, 0.3)// truewithinErrorMargin(0.2 + 0.2, 0.3)// false
上面的代码为浮点数运算,部署了一个误差检查函数。
JavaScript能够准确表示的整数范围在-253到253之间(不含两个端点),超过这个范围,无法精确表示这个值。
Math.pow(2, 53) // 90071992547409929007199254740992 // 90071992547409929007199254740993 // 9007199254740992Math.pow(2, 53) === Math.pow(2, 53) + 1// true
上面代码中,超出2的53次方之后,一个数就不精确了。
ES6引入了Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
这两个常量,用来表示这个范围的上下限。这可以用来预先检查参与运算的数值,避免超出范围。
Swift的基本数据类型###
类型标注
我们知道,Swift中用var声明变量,用let声明常量,如果赋给初始值,系统会自动判断出数据类型。
如果开始不便赋初始值,当你声明常量或者变量的时候可以加上类型标注(type annotation),说明常量或者变量中要存储的值的类型。如果要添加类型标注,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
这个例子给welcomeMessage变量添加了类型标注,表示这个变量可以存储String类型的值:
var welcomeMessage: String
声明中的冒号代表着“是...类型”,所以这行代码可以被理解为:
“声明一个类型为String,名字为welcomeMessage的变量。”
“类型为String”的意思是“可以存储任意String类型的值。”
变量现在可以被设置成任意字符串:
welcomeMessage = "Hello"
你可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型标注:
var red, green, blue: Double
注意:一般来说你很少需要写类型标注。如果你在声明常量或者变量的时候赋了一个初始值,Swift可以推断出这个常量或者变量的类型。在上面的例子中,没有给welcomeMessage赋初始值,所以变量welcomeMessage的类型是通过一个类型标注指定的,而不是通过初始值推断的。
类型安全和类型推断
Swift 是一个类型安全(type safe)的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个String
,你绝对不可能不小心传进去一个Int
。
由于 Swift 是类型安全的,所以它会在编译你的代码时进行类型检查(type checks),并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。
当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型,Swift 会使用类型推断(type inference)来选择合适的类型。有了类型推断,编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。
因为有类型推断,和 C 或者 Objective-C 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。
当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如42和3.14159。)
例如,如果你给一个新常量赋值42并且没有标明类型,Swift 可以推断出常量类型是Int,因为你给它赋的初始值看起来像一个整数:
let meaningOfLife = 42// meaningOfLife 会被推测为 Int 类型
同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是Double
:
let pi = 3.14159// pi 会被推测为 Double 类型
当推断浮点数的类型时,Swift 总是会选择Double而不是Float。如果表达式中同时出现了整数和浮点数,会被推断为Double类型:
let anotherPi = 3 + 0.14159// anotherPi 会被推测为 Double 类型
没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为Double类型。
几种数值类型
- 整数
整数就是没有小数部分的数字,比如42和-23。整数可以是有符号
(正、负、零)或者无符号(正、零)。
Swift 提供了8,16,32和64位的有符号和无符号整数类型。这些整数类型和 C 语言的命名方式很像,比如8位无符号整数类型是UInt8,32位有符号整数类型是Int32。就像 Swift 的其他类型一样,整数类型采用大写命名法。
整数范围
你可以访问不同整数类型的min和max
属性来获取对应类型的最小值和最大值:
let minValue = UInt8.min // minValue 为 0,是 UInt8 类型
let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型
min和max所传回值的类型,正是其所对的整数类型(如上例UInt8, 所传回的类型是UInt8),可用在表达式中相同类型值旁。
Int
一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型Int
,长度与当前平台的原生字长相同:在32位平台上,Int和Int32
长度相同。在64位平台上,Int和Int64长度相同。
除非你需要特定长度的整数,一般来说使用Int
就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,Int
可以存储的整数范围也可以达到-2,147,483,648~2,147,483,647,大多数时候这已经足够大了。
UInt
Swift 也提供了一个特殊的无符号类型UInt,长度与当前平台的原生字长相同:
在32位平台上,UInt和UInt32长度相同。在64位平台上,UInt和UInt64长度相同。
注意:尽量不要使用UInt,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用Int,即使你要存储的值已知是非负的。统一使用Int可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断。
- 浮点数
浮点数是有小数部分的数字,比如3.14159,0.1和-273.15。
浮点类型比整数类型表示的范围更大,可以存储比Int
类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:
Double
表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。
Float
表示32位浮点数。精度要求不高的话可以使用此类型。
注意:Double精确度很高,至少有15位数字,而Float只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围。
其他进制的表示
整数字面量可以被写作:
一个十进制数,没有前缀
一个二进制数,前缀是0b
一个八进制数,前缀是0o
一个十六进制数,前缀是0x
下面的所有整数字面量的十进制值都是17:
let decimalInteger = 17
let binaryInteger = 0b10001 // 二进制的17
let octalInteger = 0o21 // 八进制的17
let hexadecimalInteger = 0x11 // 十六进制的17
浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是0x)。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。十进制浮点数也可以有一个可选的指数(exponent),通过大写或者小写的 e来指定;十六进制浮点数必须有一个指数,通过大写或者小写的 p来指定。
如果一个十进制数的指数为exp,那这个数相当于基数和10^exp的乘积:
1.25e2表示 1.25 × 10^2,等于 125.0。
1.25e-2表示 1.25 × 10^-2,等于 0.0125。
如果一个十六进制数的指数为exp,那这个数相当于基数和2^exp的乘积:
0xFp2表示 15 × 2^2,等于 60.0。0xFp-2表示 15 × 2^-2,等于 3.75。
下面的这些浮点字面量都等于十进制的12.1875:
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0
数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量:
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
数值型类型转换
通常来讲,即使代码中的整数常量和变量已知非负,也请使用Int类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类字面量的类型推断。只有在必要的时候才使用其他整数类型,比如要处理外部的长度明确的数据或者为了优化性能、内存占用等等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。
整数转换
不同整数类型的变量和常量可以存储不同范围的数字。Int8
类型的常量或者变量可以存储的数字范围是-128127,而UInt8类型的常量或者变量能存储的数字范围是0255。如果数字超出了常量或者变量可存储的范围,编译的时候会报错:
let cannotBeNegative: UInt8 = -1// UInt8 类型不能存储负数,所以会报错
let tooBig: Int8 = Int8.max + 1// Int8 类型不能存储超过最大值的数,所以会报错
由于每种整数类型都可以存储不同范围的值,所以你必须根据不同情况选择性使用数值型类型转换。这种选择性使用的方式,可以预防隐式转换的错误并让你的代码中的类型转换意图变得清晰。
要将一种数字类型转换成另一种,你要用当前值来初始化一个期望类型的新数字,这个数字的类型就是你的目标类型。在下面的例子中,常量twoThousand
是UInt16类型,然而常量one是UInt8类型。它们不能直接相加,因为它们类型不同。所以要调用UInt16(one)来创建一个新的UInt16数字并用one
的值来初始化,然后使用这个新数字来计算:
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
现在两个数字的类型都是UInt16,可以进行相加。目标常twoThousandAndOne
的类型被推断为UInt16,因为它是两个UInt16值的和。
SomeType(ofInitialValue)
是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,UInt16
有一个构造器,可以接受一个UInt8类型的值,所以这个构造器可以用现有UInt8来创建一个新的UInt16。注意,你并不能传入任意类型的值,只能传入UInt16
内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型)。
整数和浮点数转换
整数和浮点数的转换必须显式指定类型:
let three = 3let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine// pi 等于 3.14159,所以被推测为 Double 类型
这个例子中,常量three的值被用来创建一个Double类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。浮点数到整数的反向转换同样行,整数类型可以用Double或者Float类型来初始化:
let integerPi = Int(pi)// integerPi 等于 3,所以被推测为 Int 类型
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说4.75
会变成4,-3.9会变成-3。
注意:结合数字类常量和变量不同于结合数字类字面量。字面量3可以直接和字面量0.14159相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。
字符与字符串
字符
Swift拥有Character类型,通过标明一个Character类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量:
let exclamationMark: Character = "!"
字符串
Swift拥有String类型,String是例如"hello, world","albatross"这样的有序的Character(字符)类型的值的集合。通过String类型来表示。 一个String
的内容可以用变量的方式读取,它包括一个Character值的集合。
您可以在您的代码中包含一段预定义的字符串值作为字符串字面量。字符串字面量是由双引号 ("") 包裹着的具有固定顺序的文本字符集。 字符串字面量可以用于为常量和变量提供初始值:
let someString = "Some string literal value"
注意someString常量通过字符串字面量进行初始化,Swift 会推断该常量为String类型。
初始化空字符串 (Initializing an Empty String)
要创建一个空字符串作为初始值,可以将空的字符串字面量赋值给变量,也可以初始化一个新的String。实例:
var emptyString = "" // 空字符串字面量
var anotherEmptyString = String() // 初始化方法
// 两个字符串均为空并等价。
您可以通过检查其Bool类型的isEmpty属性来判断该字符串是否为空:
if emptyString.isEmpty {
print("Nothing to see here")
}// 打印输出:"Nothing to see here"
字符串可变性 (String Mutability)
您可以通过将一个特定字符串分配给一个变量(用var)来对其进行修改,或者分配给一个常量(用let)来保证其不会被修改:
var variableString = "Horse"
variableString += " and carriage"// variableString 现在为 "Horse and carriage"
let constantString = "Highlander"constantString += " and another Highlander"// 这会报告一个编译错误 (compile-time error) - 常量字符串不可以被修改。
字符串是值类型(Strings Are Value Types)
Swift 的String类型是值类型。 如果您创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。 任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值操作。
Swift 默认字符串拷贝的方式保证了在函数/方法中传递的是字符串的值。 很明显无论该值来自于哪里,都是您独自拥有的。 您可以确信传递的字符串不会被修改,除非你自己去修改它。
在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您将字符串作为值类型的同时可以获得极高的性能。
您可通过for-in循环来遍历字符串中的characters属性来获取每一个字符的值:
for character in "Dog!".characters {
print(character)
}// D// o// g// !//
字符串可以通过传递一个值类型为Character的数组作为自变量来初始化:
let catCharacters: [Character] = ["C", "a", "t", "!", ""]
let catString = String(catCharacters)
print(catString) // 打印输出:"Cat!"
连接字符串和字符 (Concatenating Strings and Characters)
字符串可以通过加法运算符(+)相加在一起(或称“连接”)创建一个新的字符串:
let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2// welcome 现在等于 "hello there"
您也可以通过加法赋值运算符 (+=) 将一个字符串添加到一个已经存在字符串变量上:
var instruction = "look over"
instruction += string2// instruction 现在等于 "look over there"
您可以用append()方法将一个字符附加到一个字符串变量的尾部:
let exclamationMark: Character = "!"
welcome.append(exclamationMark)// welcome 现在等于 "hello there!"
这些方法是不是看着很眼熟?嗯,Swift吸收了大量现代脚本语言的做法。注意: 您不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。
字符串插值 (String Interpolation)
字符串插值是一种在字符串中插入程序表达式的方式,比起C语言的%输出法,是一种非常赞的输出结果的形式!可以在其中包含常量、变量、字面量和表达式。 您插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"
在上面的例子中,multiplier作为(multiplier)被插入到一个字符串常量量中。 当创建字符串执行插值计算时此占位符会被替换为multiplier实际的值。
multiplier的值也作为字符串中后面表达式的一部分。 该表达式计算Double(multiplier) * 2.5的值并将结果 (7.5) 插入到字符串中。 在这个例子中,表达式写为(Double(multiplier) * 2.5)并包含在字符串字面量中。
注意:
插值字符串中写在括号中的表达式不能包含非转义反斜杠 (),并且不能包含回车或换行符。不过,插值字符串可以包含其他字面量。
字符串的操作
当然,Swift拥有大量字符串操作方法。包括插入和删除、比较、索引、前后缀、计数等。
Unicode支持
作为最现代化的语言,Swift 的String和Character类型是完全兼容 Unicode 标准的,对Unicode的支持是所有语言中最完善的。
Unicode 标量(Unicode Scalars)
Swift 的String类型是基于 Unicode 标量 建立的。 Unicode 标量是对应字符或者修饰符的唯一的21位数字,例如U+0061表示小写的拉丁字母(LATIN SMALL LETTER A)("a"),U+1F425表示小鸡表情(FRONT-FACING BABY CHICK
) ("")。
字符串字面量的特殊字符 (Special Characters in String Literals)
字符串字面量可以包含以下特殊字符:
转义字符\0
(空字符)、\
(反斜线)、\t
(水平制表符)、\n
(换行符)、\r
(回车符)、"
(双引号)、'
(单引号)。
Unicode 标量,写成\u{n}(u为小写),其中n为任意一到八位十六进制数且可用的 Unicode 位码。
下面的代码为各种特殊字符的使用示例。 wiseWords常量包含了两个双引号。 dollarSign、blackHeart和sparklingHeart常量演示了三种不同格式的 Unicode 标量:
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imageination is more important than knowledge" - Enistein
let dollarSign = "\u{24}" // $, Unicode 标量 U+0024
let blackHeart = "\u{2665}" // , Unicode 标量 U+2665
let sparklingHeart = "\u{1F496}" // , Unicode 标量 U+1F496
可扩展的字形群集(Extended Grapheme Clusters)
每一个 Swift 的Character类型代表一个可扩展的字形群。 一个可扩展的字形群是一个或多个可生成人类可读的字符 Unicode 标量的有序排列。 举个例子,字母é可以用单一的 Unicode 标量é(LATIN SMALL LETTER E WITH ACUTE, 或者U+00E9)来表示。然而一个标准的字母e(LATIN SMALL LETTER E或者U+0065) 加上一个急促重音(COMBINING ACTUE ACCENT)的标量(U+0301),这样一对标量就表示了同样的字母é。 这个急促重音的标量形象的将e转换成了é。
在这两种情况中,字母é代表了一个单一的 Swift 的Character值,同时代表了一个可扩展的字形群。 在第一种情况,这个字形群包含一个单一标量;而在第二种情况,它是包含两个标量的字形群:
let eAcute: Character = "\u{E9}" // é
let combinedEAcute: Character = "\u{65}\u{301}" // e 后面加上 ́
// eAcute 是 é, combinedEAcute 是 é
可扩展的字符群集是一个灵活的方法,用许多复杂的脚本字符表示单一的Character值。
字符串的 Unicode 表示形式(Unicode Representations of Strings)
当一个 Unicode 字符串被写进文本文件或者其他储存时,字符串中的 Unicode 标量会用 Unicode 定义的几种编码格式(encoding forms)编码。每一个字符串中的小块编码都被称代码单元(code units)。这些包括 UTF-8 编码格式(编码字符串为8位的代码单元), UTF-16 编码格式(编码字符串位16位的代码单元),以及 UTF-32 编码格式(编码字符串32位的代码单元)。
Swift 提供了几种不同的方式来访问字符串的 Unicode 表示形式。 您可以利用for-in来对字符串进行遍历,从而以 Unicode 可扩展的字符群集的方式访问每一个Character值。
另外,能够以其他三种 Unicode 兼容的方式访问字符串的值:
UTF-8 代码单元集合 (利用字符串的utf8属性进行访问)
UTF-16 代码单元集合 (利用字符串的utf16属性进行访问)
21位的 Unicode 标量值集合,也就是字符串的 UTF-32 编码格式 (利用字符串的unicodeScalars属性进行访问)
下面由D,o,g,(DOUBLE EXCLAMATION MARK, Unicode 标量 U+203C)和
(DOG FACE,Unicode 标量为U+1F436)组成的字符串中的每一个字符代表着一种不同的表示:
let dogString = "Dog"
//UTF-8表示
for codeUnit in dogString.utf8 {
print("\(codeUnit) ", terminator: "")
}
print("")
// 68 111 103 226 128 188 240 159 144 182
//UTF-16表示
for codeUnit in dogString.utf16 {
print("\(codeUnit) ", terminator: "")
}
print("")
// 68 111 103 8252 55357 56374
for scalar in dogString.unicodeScalars {
print("\(scalar.value) ", terminator: "")
}
print("")
// 68 111 103 8252 128054
结论
结论:无论从表达能力、方便程度、安全度衡量,Swift在基本数据结构方面都是最完善的。