I say this as someone who watched the catastrophic results of trying to use C++ as a language to teach data structures. It is far more important to be able to think abstractly than to know how to deal with pointers.
I say this as someone who watched the catastrophic results of trying to use C++ as a language to teach data structures. It is far more important to be able to think abstractly than to know how to deal with pointers.
Unfortunately at some point, teaching OOP became all the rage in schools, and so C++ is chosen instead. That's a mistake, Java or C# should be used for teaching OOP, C should be used to teach data structures and at least some algorithms. Without hands on memory allocation, you're not really getting a full understanding of how data structures work.
I bought into this previously after hearing it constantly repeated, but don't anymore. Your point about OOP is fair, but I think there is a sane subset of C++ that is incredibly useful for teaching new programmers. One thing to be avoided is needless OOP hierarchies. User-defined types are an incredibly powerful abstraction, and makes certain programming tasks look and feel extremely natural. If you listen to Stroustrup talk about C++, this is what he tends to highlights about C++, not the advanced features. Those can come later.
C++ has plenty of warts, and I dislike certain parts of it as much as its detractors, but C++ is still an awesome language.
For me C was just one year transition between Turbo Pascal and C++, back in the mid-90's. Only used it for university assigments and on my first job. Otherwise when the option is reduced to C vs C++, I always pick C++.
For me, C was too litle when comparing with what Turbo Pascal offered me. Luckly I discovered C++ shortly after learning C.
In my experience TAing an undergrad course that used C++, almost all of the things that left students scratching their heads were things that are present in C. No garbage collector, no built-in way to determine array sizes at runtime, no way to determine if a pointer has already been deallocated, no requirement that non-void functions actually return a value on all control paths, etc. The worst thing C++ does is to amplify these problems (particularly that last one -- yes, I know, use -Wall, but someone who is just starting to use a language would not know that, and having to teach compiler flags is an even worse distraction from the subject matter of the course).
Really the problem is not language size at all. Python is a big language too, but it does not have the above problems. Common Lisp is just as big as C++ (in terms of the number of pages in the standard), yet these are not problems Lispers have. Scheme is a small language, like C, yet the elegance and expressive power of Scheme is on a completely different level from C.
The problem is that the few abstractions C presents are hard to deal with, especially for beginners, and the abstractions that C could present are sorely missed. Even the abstractions C presents are unreliable, with loads of undefined behavior and plenty of ways to break the abstractions.
Really, the fact that real-world C programmers have to pick a subset of the language and enforce various style standards and coding conventions speaks volumes about the suitability of C for beginners. If we were going to require students to use a specific subset of C, why not just write a compiler for that subset and use that to teach? The answer is pretty clear: if we were going to write a compiler for a new language that was suitable for teaching students, we would write something better. Why bother when we already have better languages to choose from? Save C for the OS course, and only as long as it remains relevant there.
The undefined behaviour is particularly bad. Its hard to tell if a wrong result is coming from a wrong algorithm or from some undefined behaviour that is silently messing up your results and this only serves to confuse students. Its also a PITA to debuig segfaults - even just getting a stack trace means that you need to use a separate debugger tool.
Another thing you didn't mention about the garbage collection is that it makes it much harder to do string handling. For example, the simple task of reading a name from standard input has multiple solutions and but all the simple ones (scanf and gets) are potentially dangerous. And this is not counting the off-by one erros in allocation because of forgetting to account for the null terminator.
Frankly, almost none of the C++11 features are actually useful for teaching data structures, and those that are relevant would only confuse students. Basically, only auto and the three kinds of smart pointers are relevant to an introductory data structures course. At the end of the day those would only create as many problems as they solve. For example, unique_ptr means that there is only one "owner," right? Wrong, get() returns a raw pointer to the object, and you can make a new unique_ptr from that. Sure it is easy to avoid -- if you are an expert with lots of C++ experience, who follows coding guidelines and all that. The data structures students had little to no C++ experience and would almost certainly have done what I just described -- and that is just one of many ways they can and will screw up C++11 features.
At the end of the day, C++ is too complicated, too poorly defined, and has all the wrong abstractions for basic CS courses.
How do you teach a basic data structure course in Python?
Pascal seems like a much better choice for data structures than C or C++. Or you can use Scala and teach functional data structures if you're adventurous ...
Then why are you teaching C++ like you are teaching C?
Use clang ?
root@iwfvm02086 ~/temp/c_test # cat test.c
#include <stdio.h>
int a_func(int a_bool)
{
if( a_bool )
{
printf("Passed variable was true\n");
}
else
{
printf("Passed variable was false\n");
return 0;
}
}
int main()
{
int num = a_func(0);
printf("In main : num = %d\n",num);
}
root@iwfvm02086 ~/temp/c_test # g++ test.c -o test
root@iwfvm02086 ~/temp/c_test # ./test
Passed variable was false
In main : num = 0
root@iwfvm02086 ~/temp/c_test # clang++ test.c -o test
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated
test.c:14:1: warning: control may reach end of non-void function [-Wreturn-type]
}
^
1 warning generated.
root@iwfvm02086 ~/temp/c_test # g++ --version
g++ (GCC) 4.4.6 20110731 (Red Hat 4.4.6-3)
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
root@iwfvm02086 ~/temp/c_test # clang++ --version
clang version 2.8 (branches/release_28)
Target: x86_64-redhat-linux-gnu
Thread model: posix
Really, the fact that real-world C programmers have to pick a subset of the language and enforce various style standards and coding conventions speaks volumes about the suitability of C for beginnersI am confused; all languages used in real world need this. Can you elaborate which languages are used in real-world that does not require coding standards ?
No garbage collector, no built-in way to determine array sizes at runtime, no way to determine if a pointer has already been deallocated
These are also side-effects for having the advantages you have teaching C for a Computer Science and Engineering course. Once you understand the fetch-decode-execute model of how a computer essentially works, it is a simple step from there to C.
I agree that, higher level concepts like Algorithms, Neural Networks are better taught with a higher level language.
eyeroll
I would recommend a language like Python, where simple data structures like lists and dictionaries can be created on a whim, freeing up students to tackle more fun problems.
Classes at the time were a mix of C, Pascal, and Lisp (at my school, YMMV). Certainly all the chances for getting pointers wrong made developing in C harder, at first. Even today I prefer to reach to Python to throw together some algorithm I am musing about. So I definitely acknowledge your point.
But, I just don't see a way around it. Today I am refactoring code to make it stay in the cache better. Doing that is not an exercise in pointless efficiency; it is the difference between the program being usable or not (it is a real time system). I contemplated compiling to assembly and eyeballing it, but a few sessions with the profiler got me the answers I needed. But to do this I had to keep in mind the pipeline architecture (cost of if statements if you don't get the branch prediction), the size of the cache, the cost of function calls, etc.
I recognize there are careers out there where you never have to touch that stuff. You write SQL calls and use a 4GL language, and so on. But what happens when your SQL runs to slow? Do you randomly vary the various server settings until it seems to run better, or do you actually understand (say) the cost/benefit of making the cache for the indexes larger? How would you talk to a piece of hardware your boss drops in your office? How..., well, you get the idea.
None of that is advocating building an entire 4 year curriculum solely on C/C++. Certainly it makes sense to do the algorithms class largely in a language like Lisp or Python. But after a certain point if you want real performance you are in C, battling low level details, and I think that is as important, if not more important, than proving the O() complexity of Fibonacci heaps.
Why is it important to teach the underlying mechanics of a computer in a data structures course, or an algorithms course, or really anything beyond OS or computer architecture courses (and perhaps a compilers course)? The reality is that the way computers work "under the hood" is counterintuitive in an extreme sense. Pointers are a counterintuitive abstraction. Fixed-width arithmetic is counterintuitive, as is having integer division always round down, as is using floating pointer numbers to represent fractions. Yes, eventually a CS student should learn about these things -- but an introductory course is the wrong place, as is basically anything that deals with purely abstract notions (data structures, algorithms, cryptography, etc.).
"...an introductory course is the wrong place"
I'm glad we're in agreement.
Any course on crypto needs to address both algorithms in the abstract and the particularities of how they're implemented in the real world.
I disagree. There is a rich theory of cryptography that is entirely abstract, for which the low-level details are an irrelevant distraction. Even the AES finalists, which were designed with low-level concerns in mind, are described abstractly and can be implemented at a high level (I have an implementation of Serpent in Common Lisp, for example -- no messing around with low-level details, just a functionally correct block cipher). Within the crypto research community there are people who work on high-level languages suitable for cryptography implementation:
http://www.charm-crypto.com/Main.html
To be fair, there is also an enormous body of work on implementing cryptosystems in the real world -- at least enough to have an entire course dedicated to the topic. If anything, we should really have two courses: an introductory course that covers the theory of cryptography, and a cryptography engineering course that deals with real-world implementations.
Specially if buffer exploits and pointer misuses are to be taken into account.
I'd say this is an extremely important part of a data structures course.
Say we have an array of integers and a linked list of integers. Which will take less time to iterate through? We all know it's the array, but you have to be aware of how caching works to know that that's the case. Which takes less space in memory? Again, we know it's the array, but you have to understand pointers to know why that's the case. If you only know what a linked list is in the abstract, then you'll have a hard time reasoning about space usage when compared to an array.
These are just a few examples, but there are countless more. It's hard to reason about how the different data structures work in the real world if you don't know how the computer works.
> The reality is that the way computers work "under the hood" is counterintuitive in an extreme sense.
Since when was CS about teaching only what's intuitive?
High level languages tend to favor one data structure over another. Starting with those languages can give students the "hammer syndrome" w.r.t. that structure.
As for preferring one data structure over another, C does that as well: arrays are the only data structure with first-class support.
I've heard this claim many, many times, but for me, I find Scheme (and Haskell, for that matter) impossible to work with. I love what Scheme and Haskell do in theory, but in practice, I find them unusable for those things.
With Scheme and Haskell, I find I'm always trying to figure out how I should make the compiler happy, vs. taking an abstract concept that I understand well and just implementing it, which is something I find easy to do with C.
But I also suck at math, and my assumption is that for people who don't suck at math, Haskell and Scheme and all the others are probably easier, and that makes sense to me.
Modula-2 and Oberon are also quite small.