CS253: Software Development with C++

Spring 2023

Basic Syntax

Show Lecture.BasicSyntax as a slide show.

CS253 Basic Syntax

The main() function

int main() {
    return 0;
}

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?
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.

Arguments

    int main(int argc, char *argv[])

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

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:

VerboseUsual style
short int a;short a;
long int b;long b;
long long int c;long long c;
unsigned int d;unsigned d;
unsigned long int e;unsigned long e;

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

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';
1732206864 3.14159 800000001

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

Aliases via typedef

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:

  1. Declare a variable: int counter;
  2. 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

Kamala Harris and husband 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: 1732206864

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: 1732206864

What type is now? I don’t care. It’s the type that time() returned.

AAA

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

LiteralTypeLiteralType
12int1.2Ffloat
34Llong3.4double
56LLlong long5.6Llong double
123Uunsigned int'x'char
234ULunsigned long"pdq" const char array (C string)
345ULLunsigned long long"xyz"sstd::string (C++ string object)
nullptrnullptr_ttrue, falsebool

Names

const auto π = 355/113.0;
cout << __VERSION__  << '\n' << __cplusplus << '\n' << π << '\n';
11.2.0
201703
3.14159

Control Flow

if

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.

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!
DescriptionFalseishTrueish
bool literalfalsetrue
Non-zero integer00ULL42
Non-zero floating-point0.00.0L6.022e22
Non-zero char'\0''x'
Non-null pointernullptrNULL"hello"

if

if (const char *p = getenv("PATH"))
    cout << "PATH is " << p << '\n';
PATH is /bin

if

if (auto now = time(nullptr); now != -1)
    cout << "Time is " << now << '\n';
Time is 1732206864

switch

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!

while

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 

dowhile

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?

dowhile

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

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.

Range-based for (alias for-each)

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