| David's profileprowyh's spaceBlogLists | Help |
|
|
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-breakswitch语句是现代程序设计语言中经典的多分支结构语句。
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来访问静态成员,则又另当别论了。
|
|
|