Show Lecture.BasicSyntax as a slide show.
CS253 Basic Syntax
The main() function
int main() {
return 0;
}
- This is a complete C program.
- main() is a function, not a method.
- Methods are functions inside of classes.
- methods ⊂ functions
- All methods are functions.
- Not all functions are methods.
- Unlike Java, C++ doesn’t have to have a class,
so main() is a function.
- We sometimes call this a free function.
- It’s not part of a class. It’s free!
Slightly more
Here’s a complete C++ program that creates some output:
#include <iostream>
using namespace std;
int main() {
cout << "Hello, world!\n";
return 0;
}
Hello, world!
How Examples Work
cout << "How do you do?" << '\n';
How do you do?
- Many examples in these slides are incomplete
snippets
of code:
- They’re turned into complete programs via webserver magic.
- You still need
#include <iostream>
.
- You still need
using namespace std;
.
- You still need
int main() {
.
- The
return 0;
is optional in main(), which is stoooopid,
so I always include it when I write main()
(unless I run out of slide space).
- You still need the
}
to close main().
- Lines of bad code are shown differently:
cout << "Hello\n";
foobar // 🦡
c.cc:2: error: ‘foobar’ was not declared in this scope
The main() function
Here’s the other valid definition of main():
int main(int argc, char *argv[]) {
// Display all arguments, including program name.
for (int i=0; i<argc; i++)
cout << "argv[" << i << "]: \"" << argv[i] << "\"\n";
return 0;
}
argv[0]: "./a.out"
That is all
Don’t even ask about void main()
.
It does not exist.
⚠ ☢ ☣ ☠️ 🦡
Return value
main() returns an int, a success/failure code to the invoker.
- 0: success
- >0: failures of various sorts.
Return different values for different failures, e.g.:
- 1: “memory cursed by a werewolf”
- 2: “fish encountered in file”
- 3: “user is ugly”
- <0: That’s just weird. Only the least-significant
eight bits of the return value count, so −1 ⇒ 255.
Arguments
int main(int argc, char *argv[])
- argc: number of arguments, including the program name as
argv[0]
.
- argc stands for “argument count”.
- argv: array of C-style strings, corresponding to program arguments.
- argv stands for “argument vector” (but it’s a C array,
not a C++ vector).
- argc is always ≥ 1, except in strange embedded environments.
- argv is always an array of C strings, even if you type
arguments that look like numbers or something else.
Arguments example
% cat ~cs253/Example/show-args.cc
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
for (int i=0; i<argc; i++)
cout << "argv[" << i << "]: " << argv[i] << '\n';
}
% g++ -Wall ~cs253/Example/show-args.cc
% ./a.out CS253: "best class ever!"
argv[0]: ./a.out
argv[1]: CS253:
argv[2]: best class ever!
% mv a.out pinkiepie
% ./pinkiepie Cupcakes
argv[0]: ./pinkiepie
argv[1]: Cupcakes
argv[0]
is the real program name, as executed—nice
for error messages.
Compatibility
- Why is
argv[]
an array of char *?
Surely they should be C++ strings?
- C++ strings didn’t exist in C.
- Why is
argv[]
an array of char *?
Surely they should be const char *?
- C didn’t have const when this was all invented.
- It’s your memory, you should be able to change it.
Basic types
C++ has a number of built-in types:
Modifiers
long, short, and unsigned are modifiers, not types. However, it’s
common to use them alone:
In a bagel shop, you don’t say “I’d like a whole wheat bagel.” You
just ask for a “whole wheat”, and the “bagel” is implied. Similarly,
int is implied.
Sizes
- The sizes of types are determined by the implementation.
- On my tiny laptop,
sizeof(long)==4
, but sizeof(long)==8
on
many computers.
- The C++ standard defines minimum sizes for types:
- Pointer types often vary.
sizeof(int *)==4
on a 32-bit machine
sizeof(int *)==8
on a 64-bit machine
Qualifiers
Qualifiers (const, constexpr, static) modify existing types.
const auto now = time(nullptr);
constexpr double PI = 3.14159265;
static long csuid = 800000000;
cout << now << ' ' << PI << ' ' << ++csuid << '\n';
1725063425 3.14159 800000001
- const: not allowed to change this
- constexpr: compile-time constant, never changes
- static: longer-than-function lifetime, private if a global
Derived types
Via pointers & arrays, a vast number of derived types exist:
int a[10]; // 10 ints
int *b; // A pointer to any number of ints
int *c[10]; // An array of 10 pointers-to-ints
int (*d)[10]; // A pointer to an array of 10 ints
If you find complex types confusing, build intermediate types
with typedef:
typedef float cash; // cash is now a synonym for float (note the order)
typedef int *intp; // New type intp, a pointer to ints
intp e[10]; // An array of 10 such pointers
With typedef, you can create an alias for an existing type.
It does not create a new type.
typedef int counter; // typedef old new;
counter c = 42;
cout << c << '\n';
42
To use typedef, think of it in two steps:
- Declare a variable:
int counter;
- Slap typedef in front of it:
typedef int counter;
Now, thanks to typedef, you’ve declared a new type
instead of a new variable.
Aliases via using
We’re familiar with using from using namespace std;
but it
can also be used to make aliases for types:
using counter = int; // using new = old;
counter c = 43;
cout << c << '\n';
43
This has the same result as using typedef, but with different syntax.
Lazy Programmer’s Declaration
Or, you can just declare variables with auto (which is not a type!):
auto i=4; // an int
auto r=3.45; // a double
cout << i+r << '\n';
7.45
But you must initialize the variable, so that the compiler knows its type:
auto foo; // 🦡
foo = 'x';
c.cc:1: error: declaration of ‘auto foo’ has no initializer
“But I initialized foo
to 'x'
!”
No, you declared foo
, and later assigned 'x'
to it.
That’s not initialization. Initialization must be the initial
operation, not the second thing that happens.
Why auto is useful
Do we care what his name is?
time_t now = time(nullptr);
cout << "Seconds since the start of 1970: " << now << '\n';
Seconds since the start of 1970: 1725063425
time_t is an alias (via typedef) for short, int, long, or
long long. It can hold whatever time() produces. Many functions
have a related type for their return value; they clutter your brain.
auto now = time(nullptr);
cout << "Seconds since the start of 1970: " << now << '\n';
Seconds since the start of 1970: 1725063425
What type is now
? I don’t care.
It’s the type that time() returned.
AAA
- I’m part of the AAA (almost always
auto
)
school of thought.
- Don’t declare your variables until you have a value for them.
- That value will determine the type of the variable.
- Sure, sometimes you can’t. Those are the exceptions.
- It can be tricky:
string a = "Alvin"; // a std::string
const char *s = "Simon"; // a const char *
char t[] = "Theodore"; // an array of char
auto d = "Dave"; // which one?
cout << sizeof(a) << '\n'
<< sizeof(s) << '\n'
<< sizeof(t) << '\n'
<< sizeof(d) << '\n';
32
8
9
8
Literals
- Use
21l
instead of 21L
to fool anyone reading your code.
- Only values, as shown above, are literals:
- Variables are not literals, even if declared const or constexpr.
- Expressions are not literals.
2+2
is not a literal.
Names
- Variable/function/method/class/type names are quite similar to Java.
- Start with a letter, continue with letters, digits, or underscore.
- Constants, by convention, are all upper case (
PI
).
- User-defined class names are often capitalized (
class Foo
);
- Avoid starting anything with an underscore (
_
),
or containing a double underscore. Those names are reserved for
the implementation.
(It’s really more complicated than that.)
- Modern C++ allows amazing Unicode variable names.
const auto π = 355/113.0;
cout << __VERSION__ << '\n' << __cplusplus << '\n' << π << '\n';
11.2.0
201703
3.14159
Control Flow
int x = 42;
if (x < 100)
cout << "This is true\n";
This is true
double pi = 3.14159265;
if (pi*pi > 10) {
cout << "Can’t even trust math any more‽\n";
}
else
cout << "Good——math hasn’t changed.\n";
Good——math hasn’t changed.
- The braces are optional, for just one statement.
- Omitting them is a good way to start an argument with a programmer.
Truth & falsity
C++ is quite liberal about what if/while/for consider to
be trueish values:
if (12 && 3.4 && 'J' && "zulu")
cout << "Great!\n";
Great!
Description | Falseish | Trueish |
bool literal | false | true |
Non-zero integer | 0 0ULL | 42 |
Non-zero floating-point | 0.0 0.0L | 6.022e22 |
Non-zero char | '\0' | 'x' |
Non-null pointer | nullptr NULL | "hello" |
- An if statement can contain a variable declaration.
The condition succeeds if the value is trueish.
if (const char *p = getenv("PATH"))
cout << "PATH is " << p << '\n';
PATH is /bin
- This is handy if the code computes a value that is only needed
inside the if, which is quite common.
- The scope of the new variable is restricted to the if statement,
even without braces. Smaller scope = better code!
- This works well if  the desired value is trueish.
- A C++17 addition: an if statement can contain a variable declaration,
much like a for loop.
- This is helpful for the same reasons as the previous example.
- Sometimes, the useful value doesn’t correspond to a trueish value,
e.g., the error value is
-1
, not 0
.
if (auto now = time(nullptr); now != -1)
cout << "Time is " << now << '\n';
Time is 1725063425
auto now = time(nullptr);
switch (localtime(&now)->tm_hour) {
case 2: case 3: case 5: case 7:
case 11: case 13: case 17: case 19:
cout << "Prime time!\n"; break;
default:
cout << "Composite time!\n"; break;
case 1:
cout << "Bed time!\n";
}
Composite time!
- switch on integer types (char, short, int, long, …).
- You can’t switch on floating-point types or strings.
- The break prevents flow into the next case.
- In C++17, a declaration is allowed before the switch value,
as in if.
char c = 'f';
while (c < 'x')
cout << c++ << ' ';
f g h i j k l m n o p q r s t u v w
- No, C++17 doesn’t allow a variable declaration.
We already have a for loop for that.
double age = 0.0;
do {
age += 1.0/13.0;
cout << age << '\n';
} while (age < 1);
0.0769231
0.153846
0.230769
0.307692
0.384615
0.461538
0.538462
0.615385
0.692308
0.769231
0.846154
0.923077
1
1.07692
Isn’t that one too many iterations?
double age = 0.0;
cout << fixed << setprecision(16);
do {
age += 1.0/13.0;
cout << age << '\n';
} while (age < 1);
0.0769230769230769
0.1538461538461539
0.2307692307692308
0.3076923076923077
0.3846153846153846
0.4615384615384616
0.5384615384615385
0.6153846153846154
0.6923076923076923
0.7692307692307692
0.8461538461538460
0.9230769230769229
0.9999999999999998
1.0769230769230766
Printing more digits shows that floating-point numbers are inherently
imprecise.
OK, we didn’t actualy “print” anything with ink on a printer.
“print” can mean “display”. Get used to it.
for (int i=0; i<5; i++)
cout << i;
01234
Declare your loop variable inside the for loop, so the scope of the
loop variable is just the for loop. There are exceptions, sure. Focus on
the 99% case.
int j;
for (j=0; j<5; j++);
cout << j; // 🦡
5
The error (extra semicolon) was not detected.
for (int k=0; k<5; k++);
cout << k; // 🦡
c.cc:2: error: ‘k’ was not declared in this scope
The error was detected.
int a[] = {11,22,33}; // C array
for (int v : a)
cout << v << ", ";
11, 22, 33,
vector<short> b = {101, 202, 303, 404}; // C++ container
for (short &n : b)
n *= 3;
for (const short n : b)
cout << n << ", ";
303, 606, 909, 1212,
for (auto q : {12, 34, 56, 78}) // Immediate list
cout << q << " and ";
12 and 34 and 56 and 78 and
for comparison
- You’re comfortable with
for (int i=0; i<foo.size(); i++)
,
so you will tend to continue using it. Think! Compare!
- Old-fashioned for:
- Declare/initialize an index variable. (int or size_t?)
- Compare the index. (
<
or <=
? foo.size()
or sizeof(foo)
?)
- Increment the index variable. (
i++
or i+1
?)
- Range-based for:
- Do this for every element in the container.
- Which sounds clearer to you🙹
- Use the old for only rarely, when you need the index variable.
- If you’re just displaying/inspecting/changing each element,
use the range-based for.