Share via


From the June 2002 issue of MSDN Magazine.

MSDN Magazine

Commas, Pseudocode, Operator =, and More
Download the code for this article: C0206.exe (56KB)
S

hrewd readers will notice that starting this month, I'm serving C# along with the usual fare. Those of you who love C/C++ needn't fret; I'll go on answering the same sorts of questions I always have. But .NET and C# really are cool in a lot of ways, so it behooves me to broaden my scope. In particular, I want to illustrate the relative merits of each system by showing how things are done both ways. That should minimize the pain of entering the new C# world, for those of you who care to. And if you're a diehard, that's fine too—you should still enjoy the column. Personally, I try to avoid ideology. I'd rather use whatever tool gets the job done quickest, and my job is to help you do the same. So—C's the day!

Q
What are the priority and associativity of the , (comma) operator? Some books state that it's right to left and others left to right. Which one is correct? How will the following statements execute?

  a=10,20,30;
  
a=(10,20,30);

 

Lakshmikumar Allampati


A
First of all, let me say that when in doubt, use parentheses. It's really not worth the two minutes it takes to look up something like this (except to satisfy your curiosity). Parens not only ensure correctness, they make your code more readable. Why leave any doubt? That said, expressions separated by commas are always evaluated left to right. So the value of an expression

  (expr1, expr2, expr3)
  

 

is always the value of the last expression. Thus, in your second example, the result of the right-hand side is 30, so a is set to 30. But because assignment (=) takes precedence over comma (,) your first example is equivalent to

  (a=10),20,30;
  

 

So the compiler-generated code first sets a to 10, then evaluates the expressions 20 and 30, which evaluate to themselves and disappear into primal nothingness since they have no side effects. In fact, a clever compiler might even omit them from the generated code. Keep in mind that while a statement like

  20;
  

 

doesn't do anything, it is nevertheless a valid C statement. Figure 1 is a short program that illustrates your example in real code. If you don't believe what I'm saying, just compile and run it. (What, you don't trust me?)
      The comma operator is rather esoteric, the kind of thing C pundits ponder in their beddy-bye and, frankly, not terribly useful. Perhaps that's why it doesn't appear in C#. I only mention the comma here because, while most programmers don't use it or even know it exists, it does crop up from time to time. Most often you'll see the little guy in for loops

  for (i=0,n=1; ...) {
  
•••
}

 

which feels fine and natural; but occasionally you'll encounter something awkward like

  c = (a++, b++, a+b);
  

 

which increments both a and b, then assigns the sum to c. One can only surmise that the person writing such code has an abnormally severe propensity for terseness or else is desperate to seem clever. Much preferable is the more mundane

  a++;      // another apple. sigh.
  
b++; // another banana—I like bananas!
c = a+b; // count = sum. Addition is cool

 

which consumes two more lines but makes your code more comprehensible at 3:00 in the morning and even gives you a chance to express your feelings about each operation. If you want to show off, write code that's well-designed and reliable.

Q
I am learning to be a programmer, and my teacher keeps saying, "You must use pseudocode before you do any programming." I know programmers who have been programming for many years and they never use it. Why do you think my teacher insists on it?

Michael Hodo


A
I'm glad you're learning to become a programmer—welcome! Today must be my day for weird questions. First the comma operator, which I haven't considered in years, and now this. Not that your question is so weird, only that I should answer it here. Usually I stick to more practical queries, not programming philosophy. Nevertheless, I frequently get "what's the best way ..." questions, so why not?
      Different programmers and different programs require different design techniques. There's nothing vital about pseudocode per se. What is important is that you have a design. What form your design takes depends on the task at hand. If you're developing algorithms or business logic, then pseudocode, flowcharts, or even mathematics may offer the most concise expression because pseudocode is inherently procedural and thus good for expressing procedural operations. But if you're building a user interface or Web site, pseudocode is too linear. You need to draw some screens and menus on a big sheet of paper or whiteboard. Even a napkin will do. Or some vaporware with commands that don't do anything. If you're designing a database, you'd draw object diagrams with tables and data structures, with relationship lines and pointers connecting them. The point is you need a design, but how you express your design depends on what you're trying to do. There's no single right way to go about writing any program.
      That said, if you're really sure you know what you're doing—and many talented programmers are—then by all means, jump right in! But I can't emphasize enough the importance of having a design. Even experienced programmers, including yours truly, have been known to botch things by being cocky. You wouldn't trek into an unknown jungle without a map, or build a skyscraper without a blueprint, would you? The design is your blueprint, both for yourself and others. It's the proof that you really do understand what you're about to do.
      "But how much time should I spend designing?" A good rule of thumb is to spend half your time on design and half implementing. Keep in mind implementation doesn't follow design in perfect succession. Usually, there's an initial design followed by the implementation. Then as soon as you get your hands dirty, you discover important details and situations your design doesn't consider. So it's back to the whiteboard.
      Design is almost always an iterative process. That's why I've never liked the organizational approach where one person or group designs and another implements. It can work if everyone stays on the same page, but the implementers need to understand the design at least well enough to recognize when some difficulty they encounter represents a design flaw, not a limitation in their coding ability. Better software is usually produced when the relationship between designer/implementer resembles that of mentor/apprentice rather than commander/footsoldier. Or better yet, when you can divide the project so each person has responsibility for both design and implementation of some piece of the overall system. Unfortunately, this isn't always possible.

Q
I have a base class with an overloaded = operator and a class derived from this base class that also has an overloaded = operator. My question is, how do I call the base class = operator from the derived class = operator?

Robert M. Larson


A
This question typically arises because operator= is not inherited when you derive a new class. For example, MFC's CString has an assignment operator for LPCSTRs:

  class CString {
  
public
const CString& operator=(LPCSTR p) { ... };
};

 

which lets you write

  CString s;
  
s = "Where do you want to go today?";

 

      Now suppose you derive a new class that is called CBetterString. Your better string doesn't automatically inherit the assignment operator from CString:

  CBetterString bs;
  
bs = "To the supermarket."; // NOT!

 

This won't compile because CBetterString does not inherit operator=(LPCSTR) from CString. If you want to assign from LPCSTRs, you have to explicitly write the assignment operator even if all it does is call the base class version. Suppose you do. What's the syntax? You might try casting as shown here:

  CBetterString& CBetterString:operator=(LPCSTR lp)
  
{
*((CString*)this) = lp; // call base class operator=
return *this;
}

 

That works, but it's too cryptic. Who can read all those parentheses? Here's a better way:

     CBetterString&     
  
CBetterString:operator=(LPCTSTR lp)
{
CString::operator=(lp);
return *this;
}

 

With this, there's no need even to document the call because it's obvious what you're doing. In general, you can always call any operator directly using the syntax classname::operatorX(...) where X is whatever operator you want (=, *, ++, whatever), and the ellipsis is whatever parameters the operator requires. An operator is just a function with the special name "operator" followed by a special character or two.

Q
How can I use C++ to get the IP address of a machine?

Abdul Ghaffar


A
First, you need to realize a machine can have more than one IP address. Many people, myself included, have computers with more than one Internet adapter card. On my machine, one adapter talks to the cable modem and the other talks to my home network. This is a typical setup for people with broadband connections.
      That said, getting the IP addresses for the local machine is straightforward, once you know the proper voodoo. It requires delving into the low-level world of Winsock, aka the Windows® Sockets API. Figure 2 shows a simple console app I wrote (called getip1) that displays the IP addresses. Figure 3 shows the output from getip1 (with fictitious data).

Figure 3 getip1 Output
Figure 3 getip1 Output

      To use Winsock, you first have to call WSAStartup to say hello. Don't forget to call WSACleanup when you're finished. To get the IP addresses, you need the machine's host name, which you get by calling—what else?—gethostname. Once you have the name, you can call gethostbyname to get more information about the host, including its IP addresses. Gethostbyname returns a pointer to a hostent structure:

  // from winsock.h
  
struct hostent {
char* h_name; /* official name of host */
char* h_aliases; /* alias list */
short h_addrtype; /* host address type */
short h_length; /* length of address */
char* h_addr_list; /* list of addresses */
};

 

As is typically the case with low-level APIs, the actual layout of this structure is a bit confusing. The hostent is actually a variable-length structure where h_addr_list is the start of a null-terminated array of addresses. Each address is h_length bytes. In the example in Figure 3, the host name (h_name) is "mars.microsoft.net." There are no aliases. The address type (or address family as it's sometimes called) is two (AF_INET = internet; see winsock.h for others). As mentioned, h_length is four since the length of an IP address is four bytes; each x.y.z.w number occupies one byte. Finally, h_addr_list is the start of the IP addresses themselves. Each one follows the other, with a null at the end. So the entire hostent structure in memory looks something like Figure 4. To format the IP addresses nicely in x.y.z.w form, you have to copy them first into something called a sockaddr, then call a special function, inet_ntoa. Hey, be thankful you don't have to read op codes!

Figure 4 Hostent Struct
Figure 4 Hostent Struct

      Just for fun, what would getip look like in C#? The .NET common runtime has a namespace, System.Net, with classes that take the pain out of network programming. In particular, there's a class called Dns with static methods to get the host name and IP addresses. Figure 5 shows the details. As you can see, getip2 is a lot simpler than the C version. Dns wraps the same basic underlying winsock functions and structures: there's Dns.GetHostName to get the host name and Dns.GetHostByName to get an IPHostEntry object. The .NET classes hide initialization and termination, hide the ugly hostent structure, wrap the addresses in an array, and even know how to format an IP address without thinking. The C# version of getip has 19 code lines, versus 53 for C. Granted, getip1 displays a bit more stuff, but C# is still the hands-down brevity winner. No need to mention it runs with a little hiccup the first time .NET loads.

Q
In your February 2002 column, I read how to pass the derived class name to the base class constructor. I'm trying to solve the same problem using C# in .NET. I ended up putting the class name as a constant being passed down to the base constructor, like this:

  Private Const DERIVED_CLASS_NAME = "DerivedClass";
  
public DerivedClass() : base(DERIVED_CLASS_NAME)
{
•••
}

 

I don't like this solution because it relies on someone typing the name correctly. Can your C++ solution be ported to C#?

Craig Roffers


A
There's no need to port my solution to C# since C# doesn't have the same problem. In C#, an object is what it is, forever and always, period. Figure 6 is a little program that demonstrates what I mean. It has two classes, MyBase and MyDerived, each with its own constructor. Each constructor calls GetType and displays the result.

Figure 7 Csclass Output
Figure 7 Csclass Output

As you can see from the output in Figure 7, GetType returns the same class from both constructors: namely, MyDerived, the derived type. In C#, the type of a class instance is whatever type the object really is: the most derived class. This is true even inside base class constructors, unlike C++. In C++, because each base class ctor initializes its own vtable, objects don't "become" what they are (they don't achieve selfhood) until all the base class constructors have completed. The opposite happens during destruction. This often leads to unexpected behavior if you call a C++ virtual function from a constructor or destructor. Control goes to the base class function, not the derived one. But as Figure 7 shows, in C#, the most derived virtual function gets called, even if you call it from a base class constructor. By the way, Csclass also shows how to walk the entire derivation tree of an object using Type.BaseType in a simple loop:

  Type type = d.GetType();
  
while (type!=null) {
Console.WriteLine(type);
type = type.BaseType;
}

 

      Of course, the real csclass.cs has extra code to print the indention—what do you think I am, a lazy slouch? Happy programming—whether it's C, C++, or C#!

Send questions and comments for Paul to cppqa@microsoft.com.

Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++(Addison-Wesley, 1992). Paul can be reached at askpd@pobox.com or https://www.dilascia.com.