头图

写在前面

这里是Ozelot。一个正在学习编程,希望和各位大佬一起交流学习的大学生。这是我关于编程语言Cesno的设计方案,欢迎大家评论交流!

提示: 最终设计尚未决定,可能还有很多设计上的不足需要优化,故内容可能会发生变动。因为没法不加标签,我不得不打了“程序员”标签。希望没有给大家造成困扰。

注意: 因为Cesno并未制作完成,这里只记录Cesno的语法规范。正因如此,代码部分的高亮可能不能保证每一次都正确显示。如果影响了阅读,我感到十分抱歉!


变量的基础

这里将要写有关于变量的前置基础知识。

标识符

标识符是编程语言中不可或缺的一个概念,它代表了一个“东西”的名字(这个东西可以是变量、函数、类……)。

Cesno的标识符和其他语言类似。标识符中可以有数字、英语字母、“$”符号以及“_”符号,但数字不能出现在标识符的第一个字符。标识符没有长度限制,也允许存在除了一些特殊字符和数字外的其他字符(因为Cesno支持UTF-8编码格式)。但为了可读性、简易性等等原因,标识符不推荐过长(比如超过了64个字符,或任何让人看了摸不着头脑的长度)的名字,也不推荐使用ASCII字符外的其他字符做标识符。

起始字符起始字符之后
数字×
英语字母
"$"、"_"

类型

类型是一种有关于数据的一种“标准”——它可以用来区分数据的种类,并且方便我们存入和读取它们。

如下是在Cesno中常用的几种类型:

类型名(英文)类型名(中文)简要解释
int整数型(或“整型”)存储-2147483648至2147483647的整数
float浮点型遵循IEEE标准中的双精度浮动小数点数
bool布尔型(或“真伪型”)存储一个真值(true)或一个伪值(false)
char字符型存储一个以UTF-8方式编码的字符
string字符串型存储一系列以UTF-8方式编码的字符
object对象型所有具体类型的基类

下表中所列是用来标识类型中的特殊状况。它们不能拿来初始化一个变量。

类型名(英文)类型名(中文)简要解释
void无类型(或“虚型”)用来标识一个函数没有返回值
any任意型(或“动态类型”)用来标识无固定类型
never空位型(或“空型”)用来标识绝对不会出现类型(void是实际无返回值,而never更倾向于逻辑层面不可能有类型信息)

如想要参阅更多类型或有关于类型的信息,请移至Cesno 类型(链接的PLACEHOLDER)。

变量的操作

声明变量

声明变量需要先写出类型的名字,其次写上变量的名字(变量名遵循标识符)。比如:

int a;

这完成了对一个整数型变量a的声明。声明变量时也可以同时给予初始值,就像这样:

int b = 10;

Cesno常用类型的声明和赋值如下:

int    x = 100;
float  d = 0.5;
bool   t = true;
char   c = 'C';
string s = "Hello, everyone.";
object o = 20; // 事实上,object类变量可以接受任何具体类型的赋值
               // 比如上面的类型都可以填入object。

如果要声明的变量没有固定的类型(即“动态类型”),可以采用关键字var。这时,Cesno不会在赋值时检查类型是否匹配。动态类型有时可以提供方便,所以请适当地使用它。Cesno的动态类型并不意味着变量“没有类型”。事实上,变量类型一直是静态的,只不过表现为动态。

var x = 3;  // 此时,x的类型由字面量 3 推导为 int 型
x += 7;     // x现在等于10,这种写法正确的
x = "str";  // x现在为 string 型,由字面量 "str" 推导而来
x = 1 / x;  // 这会报错。Cesno在此时能确定 x 在这里就是 string 型,
            // 而除号不适用于 int 与 string 一起运算。
            // 但如果程序流程包含了选择,Cesno便不能确定采用动态类型的变量的具体类型,
            // 而这可能会导致潜在的错误。所以请勿滥用 var 来定义变量。

销毁变量

当你不需要一个变量继续存在时,你可以销毁它们来节约你的内存。使用操作符delete可以轻松取消掉变量的声明,并释放内存空间。比如这样:

int a = 10;
delete a;

当然你也可以使用delete一次性释放多个变量,而且你不需要区分它们是否是数组:

int   a = 20;
int[] b = {30, 50, 100, 200};
delete a, b;

当一个使用了多态的变量将要被销毁时,无需强制转换类型:

object x = 10;
delete x;    // 正确,会释放掉 int 大小的空间,并删除 x 的声明

变量工作的范围

变量命名空间

命名空间可以用来防止名称冲突。编程人员可以采用namespace关键字加上一个标识符来划定一个命名空间。采用“.”(成员访问运算符)可以访问(包括读取和写入)命名空间内的成员,因此作用域内外可以互相访问——从外访问内,使用“.”,从内访问外,只需直接写出名字即可。可以在命名空间内声明和销毁在当前命名空间内的变量,但无法销毁外部变量。

int a = 10;
namespace n
{
    int b = 20;
    print(a + b);    // 将输出 30,其中 a = 10,b = 20,a 外 b 内
    a = 50;
    print(a + b);    // 将输出 70,其中 a = 50,b = 20,a 外 b 内
    int a = 60;
    print(a + b);    // 将输出 80,其中 a = 60,b = 20,a 内 b 内
}
print(a);      // a 是 10
print(n.a);    // a 是命名空间内成员,为 60

也可以创建匿名的命名空间,方式为不给namespace后追加标识符。这样写的特点是: 定义在匿名命名空间内的内容无法被外层访问到。比如这段代码:

int a = 10;
namespace { int anonymous_x = 20; print(anonymous_x); } // 这样写是正确的
// 在这里无法访问 anonymous_x,因为这个命名空间没有名字,你无法写出访问它的式子

变量作用域

Cesno像其他语言一样拥有作用域(scope)的概念。一个标识符可以被直接书写出来的范围就是它的作用域。作用域和命名空间、函数等关系很深。

一个变量在一个命名空间内都有效——也包括了它内部的命名空间:

int a = 10;
namespace { print(a); /* 正确,可以访问 */ }
print(a); /* 同样正确,可以访问 */

定义体内(如函数定义体)无法访问其他定义体的变量,除非它是全局变量:

global var n = 10;

class Test
{
    var member = n;    // 可以访问,n 是全局变量
}

function void changeGlobalVarN()
{
    n = "Some string";
}

void main
{
    var n = 5;               // 在 main 中覆盖了全局 n 的声明,这个 n 是局部的
    Test t1 = new Test();    // 成员 member 是 10
    changeGlovalVarN();
    Test t2 = new Test();    // 成员 member 是 "Some string"
}

由上面的例子可知: 全局变量要慎用,否则容易导致错误。


Ozelot_Vanilla
11 声望3 粉丝

希望能为更多人做出一点贡献