Coming from C
C++ grew out of C, and most C code still compiles with a C++ compiler. But "it compiles" isn't "write it that way": much of this course so far exists to replace the error-prone habits of C. This lesson puts them side by side: first the C way (shown, not run), then the C++ way (which you can run). If you never learned C, read it anyway as a review.
A small example. In C you would write:
1#include <stdio.h>2 3int main(void) {4 printf("Hello, %s! You are %d.\n", "Ana", 30);5 return 0;6}
In C++:
1#include <iostream>2#include <string>3 4int main() {5 std::string name = "Ana";6 int age = 30;7 std::cout << "Hello, " << name << "! You are " << age << ".\n";8 return 0;9}
Hello, Ana! You are 30.
cout instead of printf
printf relies on format specifiers for types: %d for an int, %f for a double. Get one wrong (%d with a double, say) and the compiler gives at most a warning, and the program shows a strange number. std::cout knows the type of every value, so there's nothing to get wrong.
For decimal places and widths, use <iomanip> from lesson 3:
1printf("Each: %.2f\n", price);2printf("Total: %8.2f\n", price * count);
1#include <iomanip>2#include <iostream>3 4int main() {5 double price = 6.5;6 int count = 3;7 std::cout << std::fixed << std::setprecision(2);8 std::cout << "Each: " << price << '\n';9 std::cout << "Total: " << std::setw(8) << price * count << '\n';10 return 0;11}
Each: 6.50 Total: 19.50
%.2f matches std::fixed << std::setprecision(2), which you set once and which stays in effect; the 8 in %8.2f matches std::setw(8), which applies only to the very next value.
Note: C headers work in C++ too: drop the .h and put a c in front. <stdio.h> becomes <cstdio>, <math.h> becomes <cmath>, <string.h> becomes <cstring>. std::printf still works, but pick one way of printing per program.
std::string instead of char arrays
A C string is a char array with a '\0' at the end. You work out whether the array is big enough, copy with strcpy, join with strcat, compare with strcmp, and one slip runs past the end.
1char full[32];2strcpy(full, "Ana");3strcat(full, " Silva");4if (strcmp(full, "Ana Silva") == 0) {5 printf("%s, %zu letters\n", full, strlen(full));6}
std::string manages its own memory and fits any length; copying is =, joining is +, comparing is ==:
1#include <iostream>2#include <string>3 4int main() {5 std::string full = "Ana";6 full += " Silva";7 if (full == "Ana Silva") {8 std::cout << full << ", " << full.size() << " characters\n";9 }10 std::string copy = full;11 copy[0] = 'E';12 std::cout << full << " / " << copy << '\n';13 return 0;14}
Ana Silva, 9 characters Ana Silva / Ena Silva
copy = full really makes a copy, so changing copy leaves full alone. In C, = between two char* just makes two pointers to the same memory.
What does this show?
1#include <iostream>2#include <string>3 4int main() {5 std::string s = "ab";6 s += "cd";7 std::cout << s.size() << '\n';8 return 0;9}
size() counts the characters, not a final '\0', and you never have to think about one. So it's 4, not the 5 you'd allow for in a C array.
vector instead of arrays
When you pass a C array to a function, only a pointer arrives; the length has to come separately:
1double average(const double *a, int n) {2 double total = 0;3 for (int i = 0; i < n; i++) total += a[i];4 return total / n;5}
A std::vector knows how many elements it has, and can grow with push_back:
1#include <iostream>2#include <vector>3 4double average(const std::vector<double>& temps) {5 double total = 0;6 for (double t : temps) {7 total += t;8 }9 return total / temps.size();10}11 12int main() {13 std::vector<double> temps{18.5, 21.0, 19.5};14 temps.push_back(23.0);15 std::cout << temps.size() << " days, average " << average(temps) << " degrees\n";16 return 0;17}
4 days, average 20.5 degrees
const std::vector<double>& means no copy and no changes, like C's const double * with the length built in.
malloc and free
In C, when the size is only known at run time, you malloc a block and free it afterward. Forgetting free leaks; freeing twice, or using memory after free, makes the hardest bugs to find.
1int n = 5;2int *squares = malloc(n * sizeof(int));3for (int i = 0; i < n; i++) squares[i] = i * i;4free(squares);
In C++, for a row of elements use a vector; for a single object on the heap, use std::unique_ptr from lesson 21. Both give the memory back on their own: no free, and no delete either:
1#include <iostream>2#include <memory>3#include <string>4#include <vector>5 6struct Account {7 std::string owner;8 double balance;9};10 11int main() {12 int n = 5;13 std::vector<int> squares(n);14 for (int i = 0; i < n; i++) {15 squares[i] = i * i;16 }17 std::cout << "Last: " << squares[n - 1] << '\n';18 auto acc = std::make_unique<Account>();19 acc->owner = "Ana";20 acc->balance = 120.5;21 std::cout << acc->owner << ": " << acc->balance << '\n';22 return 0;23}
Last: 16 Ana: 120.5
squares and acc give their memory back when main ends. Press Step and go to the end to watch the heap blocks disappear. C++ still has malloc, but it only hands you raw memory and never runs a constructor, so a member like std::string breaks inside it: don't use it in C++.
struct, nullptr, bool
In C you often typedef a struct so you don't have to write struct Point every time; a null pointer is NULL; true and false are either ints or come from <stdbool.h>.
1typedef struct {2 int x;3 int y;4} Point;5 6Point *p = NULL;7int found = 0;
In C++ a struct's name is a type name already, no typedef needed; a null pointer is nullptr; bool is built in:
1#include <iostream>2 3struct Point {4 int x;5 int y;6};7 8int main() {9 Point a{3, 4};10 Point* p = nullptr;11 bool found = false;12 if (p == nullptr) {13 p = &a;14 found = true;15 }16 std::cout << std::boolalpha << found << " " << p->x << "," << p->y << '\n';17 return 0;18}
true 3,4
In C++, NULL is really an integer 0, so when you pass it to an overloaded function, the compiler may pick the version that takes an integer, or fail to pick at all; nullptr can only be a pointer, so there's no mix-up.
References instead of pointer parameters
In C, a function that changes the caller's variable has to take a pointer: & at the call, * everywhere inside:
1void add_tax(double *price) {2 *price = *price * 1.1;3}4add_tax(&price);
C++ uses references (lesson 10): inside the function it looks like an ordinary variable, and the call needs no &:
1#include <iostream>2 3void add_tax(double& price) {4 price = price * 1.1;5}6 7int main() {8 double price = 100;9 add_tax(price);10 std::cout << "With tax: " << price << '\n';11 return 0;12}
With tax: 110
A reference is never null and can't be pointed somewhere else, so there are far fewer ways to get it wrong than with a pointer. For a big object you only read, pass a const reference, such as const std::string&.
More differences
| C | C++ |
|---|---|
(double)x | static_cast<double>(x), easy to spot as a conversion |
| no two functions with one name | same name, different parameters (overloading, lesson 9) |
#define MAX 100 | const int max_count = 100;, which has a type |
| variables declared at the top | declare where you use them, for (int i = 0; ...) |
malloc's result assigned straight to an int* | needs a cast in C++, or it won't compile; one more reason to skip malloc |
C++17 brings more (such as std::string_view); this course sticks to C++14.
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it. All three swap an old C habit for the C++ way.
A full name without strcat
✓Join first and last into a full name with std::string (one space between), and show the name and how many characters it has, like this: Ana Silva (9 characters).
Ana Silva (9 characters)
1#include <iostream>2#include <string>3 4int main() {5 std::string first = "Ana";6 std::string last = "Silva";7 8 return 0;9}
Fix it: no & with references
✓This program still has a C habit and won't compile. Fix it so it shows a=8 b=5.
a=8 b=5
1#include <iostream>2 3void swap_values(int& a, int& b) {4 int t = a;5 a = b;6 b = t;7}8 9int main() {10 int a = 5;11 int b = 8;12 swap_values(&a, &b);13 std::cout << "a=" << a << " b=" << b << '\n';14 return 0;15}
A row of numbers without malloc
✓n is a count known only at run time. Without malloc, use a std::vector<int> to hold 3 times each number from 1 to n, then show them on one line, separated by spaces.
3 6 9 12 15 18
1#include <iostream>2#include <vector>3 4int main() {5 int n = 6;6 7 return 0;8}
What you learned
printfbecomesstd::cout: no format specifiers, no type mix-ups; decimal places and widths come from<iomanip>.char[]withstrcpy/strcat/strcmpbecomesstd::stringwith=,+,==;size()doesn't count a'\0'.- An array plus a length becomes a
std::vector, passed asconst std::vector<T>&. malloc/freebecomevectorandstd::unique_ptr, which give memory back on their own; don't usemallocin C++.- No
typedeffor astruct;NULLbecomesnullptr;boolis built in. - To change the caller's variable, pass a reference, not a pointer; C headers are spelled
<cstdio>,<cmath>.