COMP345:
Advanced Program Design with C++
Lecture 2
C++ Fundamentals
Department of Computer Science and Software Engineering Concordia University
Contents
❑ Data types
❑ Variable declaration and initialization ❑ Type checking
❑ Type coercion
❑ Pointers
❑ Strings
Data types
▪ Highly similar to Java data types
▪ Basic types are not classes (like Java)
▪ Pit trap: different compilers will have different ranges for most basic data types ▪ Some programs potentially will behave differently across different platforms ▪ Hence, lack of portability of C++ programs
▪ User-defined data types using struct (as in C), as well as class (object-oriented programming)
▪ Both are allowed in the same program
▪ In fact, they are almost equivalent, but struct was kept for backward compatibility ▪ A struct can have data members, methods, constructors, destructors, etc
▪ One difference is that a struct sets its members as public by default
Data types: simple types size, range and precision
Data types: simple types size, range and precision
Data types: literals
▪ Literals
▪ 2, 5.75, ‘Z’, “Hello World“
▪ Considered “constants”: can’t change in program
▪ All literals have an inherent type that can be determined during lexical analysis
▪ Like many other languages, C++ uses escape sequences for string literals:
Contents
❑ Data types
❑ Variable declaration and initialization ❑ Type checking
❑ Type coercion
❑ Pointers
❑ Strings
Variable declaration
▪ A variable can be declared for any type valid in the current scope.
int x; double y; myClass mc;
▪ Multiple variables of the same type can be declared on the same declaration: int x,y,z;
▪ Any declared name can be referred to thereafter in the scope in which it is declared.
Variable initialization
▪ Declarations can include an optional initialization, which can use different syntactical forms:
Type a1 {v}; Type a2 = {v}; Type a3 = v; Type a4(v);
▪ All of these are widely used and apparently equivalent. ▪ However:
▪ Some are restricted to use in certain situations.
▪ Only the first one is universally usable, and is actually safer, as it implicitly does
some checking of the value passed versus the specified type.
int a1 = 1.5; //allowed using truncation
int a1 {1.5}; //not allowed, as type checking is enforced
Variable initialization
▪ In C++, “initialization” is a general concept whereby variables are created with an initial value supplied at creation.
▪ This is as opposed to the creation of variables without an initial value, which implies that a memory space is assigned to a variable, but the previously assigned content of the memory space is used.
▪ Initialization is thus safer, but is slightly time-consumptive.
▪ Initialization may seem a simple concept, but in C++ there is a proliferation of rules and special
cases related to initialization.
▪ Rules vary widely across different C++ standards.
▪ Even though you may think that you use simple initialization, expect to be faced with special/obscure cases.
▪ The following slides briefly describe the different kinds of initializations and some simple examples.
Variable initialization
▪ Default initialization: variable is constructed with no initializer.
For basic types, the previous value held in the memory space is kept.
For objects, the default constructor is called.
int x;
std::string s;
classA *objAv1 = new classA;
▪ Value initialization: variable is constructed with an empty initializer. For basic types, the value is zero-initialized.
For objects, each member is value-initialized.
int x{};
std::string s{};
classA objA = classA();
Variable initialization
▪ Direct initialization: variable is constructed using explicit constructor arguments. For basic types, no constructors, but constructor call syntax can be used.
For objects, the corresponding constructor is called
int x(4);
std::string s(“hello”);
classA objA(value1,value2,value3);
▪ Copy initialization: Variable is created using the value(s) from an existing object of the same type, or uses a converting sequence if available. Applies only to named objects.
std::string s = “hello”;
classA objAv1; classA objAv2(objAv1);
classA objAv3 = objAv1;
Copy initialization is implicitly used when passing and returning objects by value.
Variable initialization
▪ List initialization: Initializes an object from a braced initialization list.
std::string s{‘a’, ‘b’, ‘c’};
int n1{1}; // direct-list-initialization int n2 = {1}; // copy-list-initialization
▪ Reference initialization: Binds a reference to an object. char& c = a[0]
int i1; int& iref = &i1;
Reference initialization is used implicitly when a value is passed by reference or a reference is returned from a function.
Variable initialization
▪ Aggregate initialization: Initializes an aggregate from braced initialization list. It is list initialization but applied to aggregates.
▪ Simply stated, an aggregate is either: ▪ an array
▪ an object of a class that has only public members and no constructors ▪ Definition of an aggregate varies between standards.
char a[3] = {‘a’, ‘b’};
int i[3] = {1,2,3};
class aggrA{
int a, b, c; aggrB b;
class aggrB{
int x,y; }
}
aggrA a1 = {1,2,3,{4,5}};
Contents
❑ Data types
❑ Variable declaration and initialization ❑ Type checking
❑ Type coercion
❑ Pointers
❑ Strings
Type checking, type coercion, type conversion
▪ C++ uses a manifest typing strategy
▪ Variables and values are assigned types explicitly in the source code
▪ Values can only be assigned to variables declared as having the same type
▪ However, C++ allows type coercion, i.e. implicitly or explicitly changing the type of variables or values
▪ This loophole, among other things, makes C++ a weakly typed language ▪ Type mismatches
▪ General Rule: Cannot place value of one type into variable of another type
int var = 2.99; // 2 is assigned to var!
▪ Only the integer part “fits”, so that’s all that goes
▪ Called “implicit type casting” or “automatic type conversion” ▪ When using pointers or classes, much more problematic!
Explicit type casting
▪ C++ provides operators for explicit type coercion, or type casting static_cast
▪ Explicitly “casts” intVar to double type
doubleVar = static_cast
▪ Casting forces double-precision division to take place among two integer variables.
▪ Equivalent in meaning to the following C syntax, even though the C++ cast operation is checked at compile time and is thus less prone to runtime errors
doubleVar = (double)intVar1/intVar2;
Explicit type casting
• Different kinds of explicit type casting operations: static_cast
General-purpose type casting
const_cast
Cast-out “constantness”
dynamic_cast
Runtime-checked conversion of pointers and references within a single class hierarchy. Used for downcasting from a superclass to a subclass
reinterpret_cast
Implementation-dependent casting, performs a binary copy and assigns the new type to the resulting binary copied value. Highly unsafe and error-prone.
Inheritance: upcasting and downcasting
▪ When dealing with classes and subclasses, one can declare objects of a supertype and manipulate them as one of its subclasses
void displayGeometricObject(GeometricObject& g) {
cout << "The radius is " << g.getRadius() << endl;
cout << "The diameter is " << g.getDiameter() << endl; cout << "The width is " << g.getWidth() << endl;
cout << "The height is " << g.getHeight() << endl;
cout << "The area is " << g.getArea() << endl;
cout << "The perimeter is " << g.getPerimeter() << endl;
}
▪ Problem: subclass members are undefined in superclass
GeometricObject ---------------------- area
perimeter
Circle ------------
radius diameter
Rectangle ------------- width height
Inheritance: upcasting and downcasting
▪ May want to use static_cast:
void displayGeometricObject(GeometricObject& g) {
}
GeometricObject* p = &g; cout << "The radius is " cout << "The diameter is " cout << "The width is " cout << "The height is " cout << "The area is " cout << "The perimeter is "
<< static_cast
▪ Possibility of dangling pointers, wild pointers, null pointers.
▪ Pointers are unsafe, as their use may easily result in undefined behavior.
▪ References are pointer variables that eliminate some of the disadvantages of pointers, at the cost of
eliminating some of their power.
▪ Pointer arithmetic cannot be applied to a reference.
▪ Any operation applied to a reference is actually applied onto the variable it refers to, including assignment.
▪ Hence, references must be initialized upon declaration and cannot be changed afterwards.
▪ Furthermore, given a reference int& r {v1}, &r returns a pointer to the object referred to by r.
Thus, we cannot even have a pointer to a reference.
References
▪ A reference is in fact an “alias” for a memory space.
▪ Terminology: the reference is an alias to a referent (the value pointed to). ▪ Both the reference and the referee represent the same value/object.
References
▪ References are often used to pass parameters:
▪
References
▪ Or even return a value.
▪ In combination, passing a reference and returning a reference allows to pass an object, process it, then return it, allowing the returned reference to be acted upon.
Smart pointers
▪ Pointers are a very good example of a powerful C++ feature that is dangerous to use.
▪ Very powerful and lean tool to use. ▪ But leads to:
▪ Memory leaks: if a pointer is declared in a function and not deleted, the value that is pointed to is not freed. ▪ Dangling pointers: if a pointer is deleted, the pointer still points to the freed memory block.
▪ Solution: use smart pointers
▪ Reduce bugs
▪ Retain similar efficiency and syntax
▪ Control memory management by methods
▪ auto_ptr (now deprecated), unique_ptr, shared_ptr, weak_ptr ▪ Defined in the
Smart pointers
▪ Smart pointers are conceptually the same as pointers.
▪ Implemented as a template class:
▪ Class that contains a pointer of a variable type.
▪ Implements the *, ->, = operators, constructor and destructor.
template
public:
explicit auto_ptr(T* p = 0) : ptr(p) {}
};
~auto_ptr()
T& operator*()
T* operator->()
{delete ptr;}
{return *ptr;}
{return ptr;}
▪ Classes, templates and explicit constructors will be explained later.
Smart pointers
Therefore instead of writing…
void foo(){
MyClass* p(new MyClass); p->DoSomething(); delete p;
}
The programmer writes…
void foo(){
auto_ptr
p->DoSomething();
}
Smart pointers
▪ Here is an example of code which illustrates the situation of a dangling pointer:
MyClass* p(new MyClass);
MyClass* q = p;
delete p;
p->DoSomething();
p = NULL;
q->DoSomething();
// Watch out! p is now dangling!
// p is no longer dangling
// Ouch! q is still dangling!
▪ Problem: p and q are both pointing at the same address space, which is problematic.
Smart pointers
▪ For auto_ptr, this is solved by setting its pointer to NULL when it is copied:
template
auto_ptr
}
if (this != &rhs) {
delete ptr;
ptr = rhs.ptr;
rhs.ptr = NULL; }
return *this;
▪ This implementation prevents a memory space from being pointed to by two different auto_ptr. ▪ But maybe we wanted that possibility. Using smart pointers brings its limitations.
▪ There are different variations of smart pointers.
Passing parameters and returning values
▪ Parameters can be passed to a function: ▪ Byvalue
▪ A copy of the value is made, then the copied value is passed to the function.
▪ For objects, the copy constructor is called by the runtime system to make the copy.
▪ Thus, the value used in the function is local to the function.
▪ Changing it in the function does not change the value passed from the calling function. ▪ Pass by value cannot accept a parameter that is an expression.
▪ Bypointer
▪ A copy of the pointer value is made, then passed to the function.
▪ Thus, both functions are pointing to the same value; no copy of the value pointed to is made. ▪ Changing the pointed value in the called function will change the value in the calling function.
▪ Byreference
▪ Conceptually same as pass by pointer, except that the called function cannot change where the
received pointer is pointing.
▪ Drawback: cannot pass NULL , as a reference cannot be NULL
▪ Advantage: can accept unnamed values resulting from the evaluation of expressions as parameters: void f(const T& t); called as f(T(a, b, c)), or f(a+b)
▪ Call by constant reference is very often used to save memory consumption to pass parameters that are not to be changed locally.
Passing parameters and returning values
================= C::C(); ================= address where c is stored : 0068F9B3
passing by value
C::C(C); =================
= pass_by_value
= address where val_c is stored: 0068F8B8
================= passing by reference ================= = pass_by_reference
= address of object that ref_c refers to: 0068F9B3
================= passing by pointer ================= = pass_by_pointer
= address of object pointed to by
ptr_c: 0068F9B3
================= passing/returning by value
C::C(C); ================= address where val_c is stored: 0068F8B8
C::C(C);
passing/returning by reference =================
= address of object that ref_c refers to: 0068F9B3
================= passing/returning by pointer =================
address of object pointed to by ptr_c: 0068F9B3
=================
class C {
public:
C() {cout << "C::C();" << endl;};
C(int) { cout << "C::C(int);" << endl; }; C(C&) { cout << "C::C(C);" << endl; }
};
int main() {
cout << "= = = = = = = = = = = = = = = = =" << endl; C c;
cout << "= = = = = = = = = = = = = = = = =" << endl; cout << "address where c is stored : " << &c << endl;
cout << "passing by value" << endl; pass_by_value(c);
cout << "passing by reference" << endl; pass_by_reference(c);
cout << "passing by pointer" << endl; pass_by_pointer(&c);
cout << "passing/returning by value" << endl;
c = pass_and_return_by_value(c);
cout << "passing/returning by reference" << endl; c = pass_and_return_by_reference(c);
cout << "passing/returning by pointer" << endl; c = *pass_and_return_by_pointer(&c);
return 0; }
Passing parameters and returning values
void pass_by_value(C val_c) {
cout << "= = = = = = = = = = = = = = = = =" << endl;
cout << "= pass_by_value " << endl;
cout << "= address where val_c is stored: " << &val_c << endl << endl;
cout << "= = = = = = = = = = = = = = = = =" << endl;
}
void pass_by_reference(C& ref_c) {
cout << "= = = = = = = = = = = = = = = = =" << endl;
cout << "= pass_by_reference " << endl;
cout << "= address of object that ref_c refers to: " << &ref_c << endl << endl; cout << "= = = = = = = = = = = = = = = = =" << endl;
}
void pass_by_pointer(C* ptr_c) {
cout << "= = = = = = = = = = = = = = = = =" << endl;
cout << "= pass_by_pointer " << endl;
cout << "= address of object pointed to by ptr_c: " << ptr_c << endl << endl; cout << "= = = = = = = = = = = = = = = = =" << endl;
}
Passing parameters and returning values
C pass_and_return_by_value(C val_c) {
cout << "= = = = = = = = = = = = = = = = =" << endl;
cout << "address where val_c is stored: " << &val_c << endl << endl;
// returning by value returns a copy of the local object (copied using the copy constructor). return val_c;
}
C* pass_and_return_by_pointer(C* ptr_c) {
cout << "= = = = = = = = = = = = = = = = =" << endl;
cout << "address of object pointed to by ptr_c: " << ptr_c << endl << endl;
cout << "= = = = = = = = = = = = = = = = =" << endl;
// Here we return a pointer to the object. //
// Returning a pointer the original object is not really useful (though it avoids making a copy of the object).
// Really useful if we want to receive an object, do some processing using it,
// and return a new object created locally that resides on the heap,
// or sometimes receive a null pointer and return a pointer to newly created object. //
// if (ptr_c) {
// process and change the passed object
// } else {
// do some processing and create a new pointer to a C // }
return ptr_c;
}
C& pass_and_return_by_reference(C& ref_c) {
cout << "= = = = = = = = = = = = = = = = =" << endl;
cout << "= address of object that ref_c refers to: " << &ref_c << endl << endl; cout << "= = = = = = = = = = = = = = = = =" << endl;
// here we return the original reference //
// Use this if you want the calling function to use the function call as a value part of an expression.
// The most famous example of pass-and-return-by reference is the implementation
// of stream extraction/insertion operators (<>>). //
// Limitation: as a reference cannot be made to point to a new object or to be null,
// passing/returning by pointer is often the appropriate solution. return ref_c;
}
Contents
❑ Data types
❑ Variable declaration and initialization ❑ Type checking
❑ Type coercion
❑ Pointers
❑ Strings
Strings
▪ C++ provides following two types of string representations: ▪ C-style character strings
▪ string class type introduced with Standard C++
▪ Many libraries would define their own string type
▪ You will encounter many different ways of declaring/manipulating strings.
C-style character strings
▪ With C-style character strings, strings are arrays of characters terminated by a null character (‘\0’)
char greeting[6] = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’};
char greeting[] = “Hello”;
▪
▪ strcat(s1,s2): concatenate string s2 onto the end of string s1
▪ strlen(s1): returns the length of string s1
▪ strcmp(s1,s2): returns lexical distance between s1 and s2
▪ strchr(s1,ch): returns a pointer to the first occurrence of character ch in s1
▪ strstr(s1,s2): returns a pointer to the first occurrence of string s2 in string s1
Strings: example of C-style strings
#include
#include
using namespace std;
int main (){
char str1[10] = “Hello”;
char str2[10] = “World”;
char str3[10];
int len ;
// copy str1 into str3: PROBLEM!!
strcpy(str3, str1);
cout << "strcpy(str3, str1) : " << str3 << endl;
// concatenates str1 and str2: PROBLEM!!
strcat(str1, str2);
cout << "strcat(str1, str2): " << str1 << endl;
// total length of str1 after concatenation
len = strlen(str1);
cout << "strlen(str1) : " << len << endl;
return 0; }
Standard C++ string class
▪ The
▪ Provides all the operations mentioned above, using a more natural object- oriented style.
Standard C++ string class
62
Concordia University Department of Computer Science and Software Engineering
Standard C++ string class: example
#include
#include
using namespace std;
int main (){
string str1 = “Hello”;
string str2 = “World”;
string str3;
int len ;
// copy str1 into str3
str3 = str1;
cout << "str3 : " << str3 << endl;
// concatenates str1 and str2
str3 = str1 + str2;
cout << "str1 + str2 : " << str3 << endl;
// total length of str3 after concatenation len = str3.size();
cout << "str3.size() : " << len << endl;
// compare two strings
cout << "str1 is lexically " << (str1.compare(str2)) << " away from str2" << endl; return 0;
}
63
Concordia University Department of Computer Science and Software Engineering
References
▪ Y. Daniel Liang, Introduction to Programming with C++ (Chapter 1, 11, 13, 15), Peason, 2014.
▪ Bjarne Stroustrup, The C++ Programming Language (Chapter 6, 7, 11, 22), Addison-Wesley, 2013.
▪ TutorialsPoint. Learn C++ Programming Language (Chapter 17).
▪ cplusplus.com.
▪ cppreference.com. initialization.
▪ cplusplus.com. Pointers.
▪ learncpp.com. Returning by value, reference, and address.
▪ isocpp.org. References.
▪ Joey Paquet COMP345 course Notes Concordia university.