David's profileprowyh's spaceBlogLists Tools Help

Blog


    January 29

    确定性赋值(Definite Assignment)

    在C语言中,声明的局部变量如果没有初始化,或在使用之前没有赋值,则变量可以访问,但其值是不确定的(undefined),其值是栈槽(stack slot)中的随机值。
        
    C++中,局部变量的语义与C相同。
        
    但Java和C#明确规定,局部变量在访问之前必须被“确定性赋值(definitely assigned)”:Local variable must have a definitely assigned value when any access of its value occurs.
        
    下面的Java和C#代码编译都通不过,而C/C++代码可以。
        
    // C# version
        
    using System;
    public class test
    {
        public static void Main(string[] args)
        {
            flow(true);
            flow2();
            flow3();
        }
     
        private static void flow(bool flag)
        {
            int k;
     
            if (flag) k = 3;
            if (!flag) k = 4;
     
            Console.WriteLine(k);
        }
     
        private static void flow2()
        {
            int k;
            int n = 5;
     
            if (n > 2)
            {
                k = 3;
            }
     
            Console.WriteLine(k);
        }
     
        private static void flow3()
        {
            int k;
            int n = 1;
     
            while (n < 4)
            {
                k = n;
                if (k > 5) break;
                n = 6;
            }
     
            Console.WriteLine(k);
        }
    }
     
    // Java version    
     
    public class DA
    {
        public static void main(String[] args)
        {
            flow(true);
            flow2();
            flow3();
        }
     
        private static void flow(boolean flag)
        {
            int k;
     
            if (flag) k = 3;
            if (!flag) k = 4;
     
            System.out.println(k);
        }
     
        private static void flow2()
        {
            int k;
            int n = 5;
     
            if (n > 2)
            {
                k = 3;
            }
     
            System.out.println(k);
        }
     
        private static void flow3()
        {
            int k;
            int n = 1;
     
            while (n < 4)
            {
                k = n;
                if (k > 5) break;
                n = 6;
            }
     
            System.out.println(k);
        }
    }
        
    // C/C++ version
        
    #include <iostream>
    using namespace std;
     
    void flow(bool flag)
    {
        int k;
     
        if (flag) k = 3;
        if (!flag) k = 4;
     
        cout << k << endl;
    }
     
    void flow2()
    {
        int k;
        int n = 5;
     
        if (n > 2)
        {
            k = 3;
        }
     
        cout << k << endl;
    }
     
    void flow3()
    {
        int k;
        int n = 1;
     
        while (n < 4)
        {
            k = n;
            if (k > 5) break;
            n = 6;
        }
     
        cout << k << endl;
    }
     
    void main()
    {
        flow(true);
        flow2();
        flow3();
    }
     
    确定性赋值的概念强化了程序员的自觉意识,减少了隐含错误的发生。
     

    逗号运算符(comma operator)

    自从C语言引入逗号运算符以后,就成为了现代程序设计语言的经典:C++支持,自然没话说,Java支持,C#也支持!
        
    逗号运算符在某些时候确实能够带来很大的方便,下面以一例示之。
        
    ASCII字符集中,大、小写字母被分开为两个不连续的区间,现在需要将大小写字母放在一个连续的区间里。很自然地,用两个循环就可以完成任务,但如果使用逗号运算符,一个循环即可。
        
    // C/C++ version
     
    #include <iostream>
    using namespace std;
     
    char* getLetters()
    {
        const int AZ_LEN = 26;
        char *ch = new char[AZ_LEN * 2 + 1];
     
        for (int i = 0, j = 26; i < AZ_LEN; i++, j++)
        {
            ch[i] = (char)(i + 'A');
            ch[j] = (char)(j + 'a' - AZ_LEN);
        }
        ch[AZ_LEN * 2] = '\0';
     
        return ch;
    }
     
    void main()
    {
        char *cc = getLetters();
     
        for (char *p = cc; *p; p++)
        {
            if (*p == 'a')
            {
                cout << endl;
            }
            cout << *p;
        }
     
        delete cc;
    }

    // Java version
     
    public class LetterTest
    {
        public static void main(String[] args)
        {
            char[] cc = getLetters();
     
            for (char c : cc)
            {
                if (c == 'a')
                {
                    System.out.println();
                }
                System.out.print(c);
            }
        }
     
        public static char[] getLetters()
        {
            final int AZ_LEN = 26;
            char[] ch = new char[AZ_LEN * 2];
     
            for (int i = 0, j = AZ_LEN; i < AZ_LEN; i++, j++)
            {
                ch[i] = (char)(i + 'A');
                ch[j] = (char)(j + 'a' - AZ_LEN);
            }
     
            return ch;
        }
    }

    // C# version
     
    using System;
    public class Test
    {
        public static void Main(string[] args)
        {
            char[] cc = getLetters();
     
            foreach (char c in cc)
            {
                if (c == 'a')
                {
                    Console.WriteLine();
                }
                Console.Write(c);
            }
        }
     
        private static char[] getLetters()
        {
            const int AZ_LEN = 26;
            char[] ch = new char[AZ_LEN * 2];
     
            for (int i = 0, j = AZ_LEN; i < AZ_LEN; i++, j++)
            {
                ch[i] = (char)(i + 'A');
                ch[j] = (char)(j + 'a' - AZ_LEN);
            }
     
            return ch;
        }
    }
    这种并行操作缓冲区的技术是非常有用的。逗号运算符使得程序代码简洁、高效!Thanks for C's contribution.
     
    January 28

    switch-case-break

    switch语句是现代程序设计语言中经典的多分支结构语句。
     
    switch (something_happend)
    {
        case situation_1:
            statement;
            break;
        case situation_2:
            statement;
            break;
        case ...
            statement;
            break;
        case situation_k:
            statement;
            break;
        default:
            statement;
            break;
    }
     
    这个语法结构在C/C++/Java/C#中都是一样的!
     
    但每个case block中的break是否可省略,则C/C++/Java与C#是不一样的:
     
    1、C/C++/Java可省略case block中的break
     
    switch (something_happend)
    {
        case situation_1:
            statement;
        case situation_2:
            statement:
            break;
        default:
            statement;
            break;
    }
     
    此时,如果something为situation_1,则case situation_1的statement执行完后,控制即转入case situation_2,继续执行其后的statement。这在有些情况下是有必要的。但在为程序员带来方便的同时,也隐含了危险:case situation_1中省略的break是程序员的显式意图吗?抑或是偶然的疏忽?编译器无法作出判断!
     
    2、C#不能省略case block中的break
     
    C#的“不能省略”有其笨拙的地方,但确保了程序员明确表达自己的意图!
     
    就开发“bug-free”的代码而言,这点笨拙,与偶尔利用语言的语法便利“略施小技”相比,是更为重要的。
     
    C是一种“naked language”,是面向专家级的程序员的,C++为了与C兼容,也不得不如此,Java是基于C/C++重新设计的语言,完全不必如此,C#则走得更远,但对于C/C++程序员来说,却并不觉得有什么不方便。
     
    P.S.
    Java中的break可以带有标号(label),这是一种更加ugly的设计!Java去掉了goto,但为了达到非goto不能完成的任务,就让break、continue带上了label,其实更加混乱!
     
    January 27

    静态成员与实例成员(static member and instance member)

    OOPL是以class和object为核心的。
     
    为了更好地表达class、object及其关系,语言设计者又增加了许多语言级别的设施,静态成员与实例成员即是其中之一。
        
    C++将class中定义的变量称为成员(member),将class中定义的函数称为成员函数(member function)。
    Java将class中定义的变量称为字段(field),将class中定义的函数称为方法(method),统称为成员(member)。
    C#与Java相同。
        
    成员又分为静态成员与实例成员。
        
    静态成员(static member):用static关键字修饰的变量(字段)和函数(方法)。静态成员属于class。
    实例成员(instance member):没有static关键字的变量(字段)和函数(方法)。实例成员属于object(instance of class)。
        
    静态成员与实例成员的访问
        
    由于静态成员属于class,所以C++/Java/C#语言规范规定可以通过class来访问静态成员。但C++/Java也可以通过object来访问静态成员,而C#不允许。
        
    C++/Java/C#都规定必须通过object来访问实例成员,在对实例成员的访问上,三种语言是一致的。
        
    以下是三种语言的示例。
        
    // C++ version
     
    #include <iostream>
    #include <string>
     
    using namespace std;
     
    class Professional
    {
    public:
        static string ClassName;
        string InstanceName;
     
        Professional(string name)
        {
            this->InstanceName = name;
        };
     
        static void sayHello();
        void sayHi();
    };
     
    string Professional::ClassName = "Professional";
     
    void Professional::sayHello()
    {
        cout << "Hello, this is Professional." << endl;
    }
     
    void Professional::sayHi()
    {
        cout << "Hi, this is an object of Professional." << endl;
    }
     
    void main()
    {
        Professional::sayHello();
        cout << "Class name: " << Professional::ClassName << endl;
     
        Professional *professional = new Professional("C++ Programmer");
        professional->sayHi();
        cout << "Instance name: " << professional->InstanceName << endl;
     
        cout << "Class name: " << professional->ClassName << endl;
        professional->sayHello();
    }
     
    // Java version
    class Professional
    {
        public static String ClassName = "Professional";
        public String InstanceName;
     
        Professional(String name)
        {
            InstanceName = name;
        }
     
        public static void sayHello()
        {
            System.out.println("Hello, this is Professional.");
        }

        public void sayHi()
        {
            System.out.println("Hi, this is an object of Professional.");
        }
    }
     
    public class ProTest
    {
        public static void main(String[] args)
        {
            Professional.sayHello();
            System.out.println("Class name: " + Professional.ClassName);
     
            Professional professional = new Professional("Programmer");
            professional.sayHi();
            System.out.println("Instance name: " + professional.InstanceName);

            System.out.println("Class name: " + professional.ClassName);
            professional.sayHello();
        }
    }
     
    // C# version
    using System;
    class Professional
    {
        public static string ClassName = "Professional";
        public string InstanceName;
     
        public Professional(string name)
        {
            InstanceName = name;
        }
     
        public static void SayHello()
        {
            Console.WriteLine("Hello, this is Professional.");
        }
        public void SayHi()
        {
            Console.WriteLine("Hi, this is an object of Professional.");
        }
    }
     
    public class ProTest
    {
        public static void Main(string[] args)
        {
            Professional.SayHello();
            Console.WriteLine("Class name: " + Professional.ClassName);
     
            Professional professional = new Professional("Programmer");
            professional.SayHi();
            Console.WriteLine("Instance name: " + professional.InstanceName);

            Console.WriteLine("Class name: " + professional.ClassName);    // compile-time error
            professional.SayHello();                                                           // compile-time error
        }
    }

    由此看来,C++/Java和C#在对待静态成员的问题上是不同的。仔细想想,各有道理。
     
    在C++/Java看来,静态成员属于class,object是class的一个实例,静态成员对于该class的所有object都是相同的,所以通过object能够访问静态成员也属合情合理。
     
    但C#将静态成员的“静态”语义贯彻地比较彻底,静态成员既然属于class,就应该通过class来访问,如果也能够通过object访问,则容易与实例成员的访问混淆。在上例中,如果静态方法SayHello()也能够通过obect来访问,则professional.SayHello()与professional.SayHi()在语法上没有任何区别,那如何知道哪个是静态方法,哪个又是实例方法?
     
    语言语法应该有助于程序员意图的表达。
     
    从语法与语义的一致性和对程序员友好性的角度而言,C#的做法是更可取的。当然,你可以在C++/Java中坚持通过class来访问静态成员,则又另当别论了。