Log C++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Cap1: Introduction to Objects
- boundaries in a class = C++ uses three explicit keywords to set the boundaries in a class: public, private, and protected. Their use and meaning are
quite straightforward. These access specifiers determine who can use the definitions that follow. public means the following definitions are available
to everyone. The private keyword, on the other hand, means that no one can access those definitions except you, the creator of the type, inside member
functions of that type. private is a brick wall between you and the client programmer. If someone tries to access a private member, they’ll get a
compile-time error. Protected acts just like private, with the exception that an inheriting class has access to protected members, but not private members.
- composition = composing a new class from existing classes.
- inheritance = we take the existing class,clone it, and then make additions and modifications to the clone. If the original class (called parent class)
is changed, the modified “clone” (called the derived or inherited or child class) also reflects those changes. When you inherit from an existing type, you
create a new type. This new type contains not only all the members of the existing type (although the private ones are hidden away and inaccessible),
but more importantly it duplicates the interface of the base class. The derived class is the same type as the base class.
You have two ways to differentiate your new derived class from the original base class. The first is quite straightforward: You simply add brand new
functions to the derived class. The second and more important way to differentiate your new class is to change the behavior of an existing base-class
function. This is referred to as overriding that function.
- virtual keyword = member functions are dynamically bound. Virtual functions allow you to express the differences in behavior of classes in the same
family.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Cap2: Making & Using Objects
- An interpreter translates source code into activities (which may comprise groups of machine instructions) and immediately executes those activities.
- A compiler translates source code directly into assembly language or machine instructions.
- A declaration introduces a name – an identifier – to the compiler. It tells the compiler “This function or this variable exists somewhere, and here is
what it should look like.”. You can declare a variable or a function in many different places, but there must be only one definition in C++.
- A definition, on the other hand, says: “Make this variable here” or “Make this function here.” It allocates storage for the name. A definition can also
be a declaration. If the compiler hasn’t seen the name x before and you define int x;, the compiler sees the name as a declaration and allocates storage
for it all at once.
- A function declaration in C and C++ gives the function name, the argument types passed to the function, and the return value of the function.
Ex: int func1(int,int);
- Arguments in function declarations may have names. The compiler ignores the names but they can be helpful as mnemonic devices for the user.
Ex: int func1(int length, int width);
- Function definitions look like function declarations except that they have bodies. A body is a collection of statements enclosed in braces. Notice that
in the function definition, the braces replace the semicolon. Notice also that the arguments in the function definition must have names if you want to
use the arguments in the function body.
Ex: int func1(int length, int width) { }
- A variable declaration tells the compiler what a variable looks like. It says, “I know you haven’t seen this name before, but I promise it exists
someplace, and it’s a variable of X type.”
Ex: int a;
- The keyword: extern. It can mean the definition is external to the file, or that the definition occurs later in the file.
- A variable must be declared using the extern keyword like this: extern int a; extern can also apply to function declarations but is superfluous and
optional.
- A header file is a file containing the external declarations for a library. To include a header file, use the #include preprocessor directive. This
tells the preprocessor to open the named header file and insert its contents where the #include statement appears.
Ex: #include <header> ; #include "local.h" - search for the file relative to the current directory. If the file is not found, then the include
directive is reprocessed as if it had angle brackets instead of quotes.
- The linker collects object modules (which often use file name extensions like .o or .obj), generated by the compiler, into an executable program the
operating system can load and run. It is the last phase of the compilation process.
- To use a library: Include the library’s header file; Use the functions and variables in the library; Link the library into the executable program.
- The iostream package automatically defines a variable (an object) called cout (which is short for “console output”) that accepts all data bound for
standard output. To send data to standard output, you use the operator <<.
- the namespace keyword: Each set of C++ definitions in a library or program is “wrapped” in a namespace, and if some other definition has an identical
name, but is in a different namespace, then there is no collision.
- The keyword using: It allows you to say “I want to use the declarations and/or definitions in this namespace.” All of the Standard C++ libraries
are wrapped in a single namespace, which is std (for “standard”).
Ex: using namespace std;
- When the program starts, it executes initialization code and calls a special function, “main( )” You put the primary code for the program here. In C++,
main( ) always has return type of int.
- C comments start with /* and end with */. They can include newlines. C++ uses C-style comments and has an additional type of comment: //. The // starts
a comment that terminates with a newline.
- The special iostream function endl outputs the line and a newline.
- Inside a character array, you can insert special characters by using escape sequences. These consist of a backslash (\) followed by a special code. For
example \n means newline. Others include \t (tab).
- The output formatting available with iostreams also includes features such as number formatting in decimal, octal, and hexadecimal.
Ex: cout << "a number in decimal: "<< dec << 15;cout << "in octal: " << oct << 15; cout << "in hex: " << hex << 15; cout << "non-printing char
(escape): "<< char(27); = the iostreams class printing numbers in decimal, octal, and hexadecimal using iostream manipulators (which don’t print
anything, but change the state of the output stream).
- An important feature of the C preprocessor is character array concatenation. If two quoted character arrays are adjacent, and no punctuation is between
them, the compiler will paste the character arrays together into a single character array.
- The object used for standard input is cin (for “console input”). The call to cin.get() function waits for you to press Enter
- Any program can be called from inside a C or C++ program using the Standard C system() function, which is declared in the header file <cstdlib>.
- The Standard C++ string class is designed to take care of (and hide) all the low-level manipulations of character arrays. The string class is in the
namespace std so a using directive is necessary.
Ex: string s3 = "Hello, World.";
- To combine strings you simply use the ‘+’ operator, which also allows you to combine character arrays with strings.
- Reading and writing files: To open files for reading and writing, you must include <fstream>. To open a file for reading, you create an ifstream object
(Ex: ifstream in("Scopy.cpp");), which then behaves like cin. To open a file for writing, you create an ofstream object, which then behaves like cout.
- One of the most useful functions in the iostream library is getline(), which allows you to read one line (terminated by a newline) into a string object.
The first argument is the ifstream object you’re reading from and the second argument is the string object. When the function call is finished, the string
object will contain the line.
Ex: ifstream in("Scopy.cpp"); string s; getline(in, s);
- Vector : a basic standard container. The vector class is a template, which means that it can be efficiently applied to different types. That is, we can
create a vector of shapes, a vector of cats, a vector of strings, etc. Basically, with a template you can create a “class of anything.” To tell the
compiler what it is that the class will work with (in this case, what the vector will hold), you put the name of the desired type in “angle brackets,”.
So a vector of string would be denoted vector<string>. To use a vector, you include the header file <vector>.
Ex: vector<string> v;
- Since vector expresses the concept of a “container,” there must be a way to put things into the container and get things back out of the container.
To add a brand-new element on the end of a vector, you use the member function push_back(). Ex: v.push_back(string);
How do you get these elements back out again? The solution is: operator overloading is used to make the vector look like an array. The
indexing notation [] is incorporated into the vector using operator overloading. Ex: v[i];
O functie utila este .size() folosita pentru determinarea marimii vectorului. Ex: int x = v.size();
- You also have the ability to assign (and thus to change) any element of a vector, also through the use of the square-brackets indexing operator.
Ex: v[i] = v[i] * 10;
- When you call resize method on a vector, it truncates the sequence if the new size is smaller, or it appends “zeroes”, if the number is larger. You can
even resize vectors of strings and get empty strings added. Ex: v3.resize(v1.size()); // pre-allocate space
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Cap3: The C in C++
- With function prototyping, you must use a description of the types of arguments when declaring and defining a function. This
description is the “prototype.”
Ex: float f3(float, int, char, double); // Returns a float
- When you don’t know how many arguments or what type of arguments you will have for a function, you use a variable argument list.
This “uncertain argument list” is represented by ellipses (...). You should restrict your use of variable argument lists to C.
- To return a value from a function, you use the return statement. You can have more than one return statement in a function
definition: Ex: char cfunc(int i) { if(i == 0) return 'a';}
- All conditional statements use the truth or falsehood of a conditional expression to determine the execution path. An example of
a conditional expression is A == B. The expression produces a Boolean true or false.
Other conditional operators are: "!=" (the "not-equal" conditional), >, <, >=, etc.
- The if-else statement can exist in two forms: with or without the else. The two forms are: if(expression) statement or if(expression)
statement else statement. The “expression” evaluates to true or false. The “statement” means either a simple statement terminated by a
semicolon or a compound statement, which is a group of simple statements enclosed in braces.
- while, do-while, and for control looping. A statement repeats until the controlling expression evaluates to false.
The form of a while loop is: while(expression) statement. The expression is evaluated once at the beginning of the loop and again
before each further iteration of the statement. The while(true) statement is the equivalent of saying “do this loop forever.” The break statement
allows you to break out of this infinite while loop.
The form of do-while is: do statement while(expression); The do-while is different from the while because the statement
always executes at least once, even if the expression evaluates to false the first time.
A for loop performs initialization before the first iteration. Then it performs conditional testing and, at the end of each iteration,
some form of “stepping.” The form of the for loop is: for(initialization; conditional; step) statement; The initialization code executes
once at the very beginning. The conditional is tested before each iteration (if it evaluates to false at the beginning, the statement
never executes). At the end of each loop, the step executes.
- Inside the body of any of the looping constructs while, do-while, or for, you can control the flow of the loop using break and continue.
- break quits the loop without executing the rest of the statements in the loop. continue stops the execution of the current iteration
and goes back to the beginning of the loop to begin a new iteration.
- A switch statement selects from among pieces of code based on the value of an integral expression. Its form is:
switch(selector) { case integral-value1 : statement; break; case integral-value2 : statement; break; default: statement; }
Selector is an expression that produces an integral value. The switch compares the result of selector to each integral value. If no match occurs,
the default statement executes.
- Recursion is an interesting and sometimes useful programming technique whereby you call the function that you’re in.
- You can think of operators as a special type of function. An operator takes one or more arguments and produces a new value.
Operator precedence defines the order in which an expression evaluates when several different operators are present. Multiplication and division happen
before addition and subtraction.
The auto-decrement operator is ‘--’ and means “decrease by one unit.” The auto-increment operator is ‘++’ and means “increase by one unit.”. If A is an
int, for example, the expression ++A is equivalent to (A = A + 1). If the operator appears before the variable, (i.e., ++A), the operation is first
performed and the resulting value is produced.
- Data types define the way you use storage (memory) in the programs you write. By specifying a data type, you tell the compiler how to
create a particular piece of storage, and also how to manipulate that storage. Data types can be built-in or abstract.
A built-in data type is one that the compiler intrinsically understands, one that is wired directly into the compiler.
A user-defined data type (or abstract data types) is one that you create as a class. The compiler knows how to handle built-in
types when it starts up; it “learns” how to handle abstract data types by reading header files containing class declarations.
- C++ have four basic built-in data types. A char is for character storage and uses a minimum of 8 bits (one byte) of storage.
An int stores an integral number and uses a minimum of two bytes of storage(4 bytes for my machine). The float and double types store floating-point
numbers, usually in IEEE floating-point format. float is for singleprecision floating point and double (8 bytes) is for double-precision floating point.
- The Standard C++ bool type can have two states expressed by the built-in constants true (which converts to an integral one) and false
(which converts to an integral zero).
- Specifiers modify the meanings of the basic built-in types and expand them to a much larger set. There are four specifiers: long,
short, signed, and unsigned. long and short modify the maximum and minimum values that a data type will hold. The size hierarchy for integral
types is: short int, int, long int. The size hierarchy for floating point numbers is: float, double, and long double. “long float”
is not a legal type. There are no short floating-point numbers. The signed and unsigned specifiers tell the compiler how to use
the sign bit with integral types and characters (floating-point numbers always contain a sign). An unsigned number does not keep track of the
sign and thus has an extra bit available, so it can store positive numbers twice as large as the positive numbers that can be stored in a signed number.
- Memory is typically laid out as a sequential series of memory locations; we usually refer to these locations as eight-bit bytes but
actually the size of each space depends on the architecture of the particular machine and is usually called that machine’s word size.
Each space can be uniquely distinguished from all other spaces by its address. Since your program lives in memory while it’s being run,
every element of your program has an address. There is an operator in C++ that will tell you the address of an element. This is the ‘&’ operator.
Ex: int dog; cout << "dog: " << (long)&dog; (long) - is a cast. It says “Don’t treat this as if it’s normal type, instead treat it as a long.”
- C++ have a special type of variable that holds an address. This variable is called a pointer. The operator that defines a pointer is the same
as the one used for multiplication: ‘*’. When you define a pointer, you must specify the type of variable it points to. A pointer to an int
looks like this: int* ip; // ip points to an int variable.
A general guideline for C++ programming is that you should always initialize a variable at the point of definition.
For example: int a = 47; int* ipa = &a; - Now both a and ipa have been initialized, and ipa holds the address of a.
- Once you have an initialized pointer, the most basic thing you can do with it is to use it to modify the value it points to. To access a
variable through a pointer, you dereference the pointer using the same operator that you used to define it, like this: *ipa = 100;
Now a contains the value 100 instead of 47. The basics of pointers: you can hold an address, and you can use that address to modify the original variable.
Why do you want to modify one variable using another variable as a proxy? To change “outside objects” from within a function.
- Ordinarily, when you pass an argument to a function, a copy of that argument is made inside the function. This is referred to as pass-by-value.
If we pass a pointer into a function instead of an ordinary value ("void f(int* p){}"), we are actually passing an alias to the outside object,
enabling the function to modify that outside object.
- C++ adds an additional way to pass an address into a function. This is pass-by-reference. The difference between references and pointers is that
calling a function that takes references is cleaner, syntactically, than calling a function that takes pointers (and it is exactly this syntactic
difference that makes references essential in certain situations). Ex. pass-by-reference: void f(int& r) {r = 5;} int main() {int x = 47; f(x); cout << x;}
So you can see that pass-by-reference allows a function to modify the outside object, just like passing a pointer does. Thus, for this simple introduction
you can assume that references are just a syntactically different way to accomplish the same thing that pointers do: allow functions to change outside
objects.
- If you state that a pointer is a void*, it means that any type of address at all can be assigned to that pointer (whereas if you have an int*,
you can assign only the address of an int variable to that pointer). Once you assign to a void* you lose any information about what type it is.
This means that before you can use the pointer, you must cast it to the correct type.
Ex: int i = 99; void* vp = &i; *((int*)vp) = 3; The cast (int*)vp takes the void* and tells the compiler to treat it as an int*, and thus it can be
successfully dereferenced. In general, void pointers should be avoided.
- Scoping rules tell you where a variable is valid, where it is created, and where it gets destroyed (i.e., goes out of scope). The scope of a
variable extends from the point where it is defined to the first closing brace that matches the closest opening brace before the variable was defined.
That is, a scope is defined by its “nearest” set of braces. C++ (not C) allows you to define variables anywhere in a scope, so you can define a
variable right before you use it.
- When creating a variable, you have a number of options to specify the lifetime of the variable, how the storage is allocated for that
variable, and how the variable is treated by the compiler.
- Global variables are defined outside all function bodies and are available to all parts of the program (even code in other files).
Global variables are unaffected by scopes and are always available (i.e., the lifetime of a global variable lasts until the program ends).
If the existence of a global variable in one file is declared using the extern keyword in another file, the data is available for use by the
second file.
- Local variables occur within a scope; they are “local” to a function. They are often called automatic variables because they automatically
come into being when the scope is entered and automatically go away when the scope closes. Local variables exist only temporarily, on the stack,
while a function is being called. The linker doesn’t know about automatic variables, and so these have no linkage.
- A register variable is a type of local variable. The register keyword tells the compiler “Make accesses to this variable as fast as possible.”
Increasing the access speed is implementation dependent, but, as the name suggests, it is often done by placing the variable in a register.
There is no guarantee that the variable will be placed in a register or even that the access speed will increase. It is a hint to the compiler.
There are restrictions to the use of register variables. You cannot take or compute the address of a register variable. A register variable can be
declared only within a block (you cannot have global or static register variables). You can, however, use a register variable as a formal argument
in a function (i.e., in the argument list). In general, you shouldn’t try to second-guess the compiler’s optimizer, since it will probably do a better
job than you can. Thus, the register keyword is best avoided.
- The static keyword has several distinct meanings. Normally, variables defined local to a function disappear at the end of the function scope.
When you call the function again, storage for the variables is created anew and the values are re-initialized. If you want a value to be extant
throughout the life of a program, you can define a function’s local variable to be static and give it an initial value. The initialization is
performed only the first time the function is called, and the data retains its value between function calls. This way, a function can “remember” some
piece of information between function calls. You may wonder why a global variable isn’t used instead. The beauty of a static variable is that it is
unavailable outside the scope of the function, so it can’t be inadvertently changed. This localizes errors. The second meaning of static is related to
the first in the “unavailable outside a certain scope” sense. When static is applied to a function name or to a variable that is outside of all
functions, it means “This name is unavailable outside of this file.” The function name or variable is local to the file; we say it has file scope.
- The extern keyword tells the compiler that a variable or a function exists, even if the compiler hasn’t yet seen it in the file currently
being compiled. This variable or function may be defined in another file or further down in the current file.
- To understand the behavior of C++ programs, you need to know about linkage. In an executing program, an identifier is represented by storage in
memory that holds a variable or a compiled function body. Linkage describes this storage as it is seen by the linker. There are two types of linkage:
internal linkage and external linkage. Internal linkage means that storage is created to represent the identifier only for the file being compiled.
Other files may use the same identifier name with internal linkage, or for a global variable, and no conflicts will be found by the linker – separate
storage is created for each identifier. External linkage means that a single piece of storage is created to represent the identifier for all files
being compiled. The storage is created once, and the linker must resolve all other references to that storage. Global variables and function names
have external linkage. These are accessed from other files by declaring them with the keyword extern.
- The modifier const tells the compiler that a name represents a constant. Any data type, built-in or user-defined, may be defined as const. If you
define something as const and then attempt to modify it, the compiler will generate an error. You must specify the type of a const, like this:
const int x = 10; . In C++, you can take the address of a const. A const has a scope, just like a regular variable, so you can “hide” a const inside a
function and be sure that the name will not affect the rest of the program. A const must always have an initialization value. Character constants are
characters surrounded by single quotes, as: ‘A’. Special characters are represented with the “backslash escape”: ‘\n’ (newline), ‘\t’ (tab),
‘\\’ (backslash), ‘\r’ (carriage return), ‘\"’ (double quotes), ‘\'’ (single quote), etc.
- The qualifier volatile tells the compiler “You never know when this will change,” and prevents the compiler from performing any optimizations based
on the stability of that variable. Use this keyword when you read some value outside the control of your code, such as a register in a piece of
communication hardware. A volatile variable is always read whenever its value is required, even if it was just read the line before. A special case
of some storage being “outside the control of your code” is in a multithreaded program. If you’re watching a particular flag that is modified by another
thread or process, that flag should be volatile so the compiler doesn’t make the assumption that it can optimize away multiple reads of the flag.
- All operators produce a value from their operands. This value is produced without modifying the operands, except with the assignment, increment, and
decrement operators. Modifying an operand is called a side effect. Assignment is performed with the operator "=" It means “Take the right-hand side
(often called the rvalue) and copy it into the lefthand side (often called the lvalue)”. The basic mathematical operators: addition (+), subtraction (-),
division (/), multiplication (*), and modulus (%) - this produces the remainder from integer division. Integer division truncates the result (it doesn’t
round). The modulus operator cannot be used with floating-point numbers. C++ also use a shorthand notation to perform an operation and an assignment
at the same time. For example, to add 4 to the variable x and assign x to the result, you say: x += 4;
- Preprocessor macros are used to save typing and are traditionally named with all uppercase letters so they stand out.
Ex: // A macro to display a string and a value. #define PRINT(STR, VAR) cout << STR " = " << VAR << endl int main() {int i; PRINT("i",i);
The arguments in the parenthesized list following the macro name are substituted in all the code following the closing parenthesis. The preprocessor
removes the name PRINT and substitutes the code wherever the macro is called, so the compiler cannot generate any error messages using the macro name,
and it doesn’t do any type checking on the arguments. A macros can use a preprocessor feature called stringizing (implemented with the ‘#’ sign before an
expression) that takes any expression and turns it into a character array. Ex: #define P(EX) cout << #EX << ": " << EX << endl;
- Relational operators establish a relationship between the values of the operands. The relational operators are: less than (<), greater than (>),
less than or equal to (<=), greater than or equal to (>=), equivalent (==), and not equivalent (!=).
- The logical operators and (&&) and or (||) produce a true or false based on the logical relationship of its arguments. Remember that in C++,
a statement is true if it has a non-zero value, and false if it has a value of zero.
- The bitwise operators allow you to manipulate individual bits in a number (since floating point values use a special internal format, the bitwise
operators work only with integral types: char, int and long). Bitwise operators perform Boolean algebra on the corresponding bits in the arguments
to produce the result. The bitwise and operator (&) produces a one in the output bit if both input bits are one; otherwise it produces a zero.
The bitwise or operator (|) produces a one in the output bit if either input bit is a one and produces a zero only if both input bits are zero.
The bitwise exclusive or, or xor (^) produces a one in the output bit if one or the other input bit is a one, but not both.
The bitwise not (~) is a unary operator – it only takes one argument (all other bitwise operators are binary operators).
Bitwise operators can be combined with the "=" sign to unite the operation and assignment: &=, |=, and ^= are all legitimate operations.
- The shift operators also manipulate bits. The left-shift operator (<<) produces the operand to the left of the operator shifted to the left
by the number of bits specified after the operator. The right-shift operator (>>) produces the operand to the left of the operator shifted
to the right by the number of bits specified after the operator. Shifts can be combined with the equal sign (<<= and >>=). The lvalue is replaced
by the lvalue shifted by the rvalue.
- Unary operators: Bitwise not isn’t the only operator that takes a single argument. Its companion, the logical not (!), will take a true value
and produce a false value. The unary minus (-) and unary plus (+) are the same operators as binary minus and plus. Ex: x = -a; The unary minus produces
the negative of the value. Unary plus provides symmetry with unary minus, although it doesn’t actually do anything.
The increment and decrement operators (++ and --) increase or decrease the variable by one unit, although “unit” can have different meanings according
to the data type – this is especially true with pointers. The last unary operators are the address-of (&), dereference (* and - >), cast operators ,
,new and delete.
- The ternary operator: The ternary if-else is unusual because it has three operands. It is truly an operator because it produces a value, unlike
the ordinary if-else statement. It consists of three expressions: if the first expression (followed by a ?) evaluates to true, the expression
following the ? is evaluated and its result becomes the value produced by the operator. If the first expression is false, the third expression
(following a :) is executed and its result becomes the value produced by the operator.
Ex1: int i = 1, j = 2; cout << ( i > j ? i : j ) << " is greater." << endl; Ex1: cout << (i == 5 ? "egalitate" : (i > 5 ? "MaiMare" : "MaiMic"));
- Casting operators: The word cast is used in the sense of “casting into a mold.” The compiler will automatically change one type of data into another
if it makes sense. For instance, if you assign an integral value to a floating-point variable, the compiler will secretly call a function to convert
the int to a float. Casting allows you to make this type conversion explicit, or to force it when it wouldn’t normally happen. To perform a cast,
put the desired data type inside parentheses to the left of the value. This value can be a variable, a constant, the value produced by an expression,
or the return value of a function. Ex: int main() {int b = 200; unsigned long a = (unsigned long int)b;}
C++ has an additional casting syntax, which follows the function call syntax. This syntax puts the parentheses around the argument, like a function call,
rather than around the data type: float a = float(200);
Casts should be used carefully, because what you are actually doing is saying to the compiler “Forget type checking – treat it as this other type instead.”
In general, casts should be few and isolated to the solution of very specific problems. Standard C++ includes an explicit cast syntax that can be used
to completely replace the old C-style casts: static_cast, const_cast, reinterpret_cast, dynamic_cast (For type-safe downcasting).
- A static_cast is used for all conversions that are well-defined. These include “safe” conversions that the compiler would allow you to do without a cast
and less-safe conversions that are nonetheless welldefined. The types of conversions covered by static_cast include typical castless conversions,
narrowing (information-losing) conversions, forcing a conversion from a void*, implicit type conversions, and static navigation of class hierarchies.
Ex1: int i; long l; l = static_cast<long>(i); Promoting from an int to a long or float is not a problem because the latter can always hold every value
that an int can contain. Although it’s unnecessary, you can use static_cast to highlight these promotions.
Ex2: int a[3]; void* pa = static_cast<void*>(&a[0]); Converting back the other way you can lose data because an int is not as “wide” as a long or a float;
Thus these are called narrowing conversions. The compiler will still perform these, but will often give you a warning. You can eliminate this warning
and indicate that you really did mean it using a cast. Assigning from a void* is not allowed without a cast in C++ and is dangerous.
Ex: void* vp = &i; float* fp = (float*)vp;
- const_cast: If you want to convert from a const to a nonconst or from a volatile to a nonvolatile, you use const_cast.
Ex1: const int i = 0; int* j = (int*)&i; // Vechea forma; j = const_cast<int*>(&i); // Forma preferata
Ex2: volatile int k = 0; int* u = const_cast<int*>(&k); If you take the address of a const object, you produce a pointer to a const, and this cannot be
assigned to a nonconst pointer without a cast. The old-style cast will accomplish this, but the const_cast is the appropriate one to use.
The same holds true for volatile.
- reinterpret_cast: This is the least safe of the casting mechanisms, and the one most likely to produce bugs. A reinterpret_cast pretends that an object
is just a bit pattern that can be treated (for some dark purpose) as if it were an entirely different type of object. A reinterpret_cast often indicates
inadvisable and/or nonportable programming.
- The sizeof operator gives you information about the amount of memory allocated for data items. sizeof tells you the number of bytes used by any
particular variable. Ex: cout << "sizeof(double) = " << sizeof(double);
Note that sizeof is an operator, not a function. If you apply it to a type, it must be used with the parenthesized form, but if you apply it to a
variable you can use it without parentheses: Ex: int x; int i = sizeof x;. sizeof can also give you the sizes of user-defined data types.
- The asm keyword: This is an escape mechanism that allows you to write assembly code for your hardware within a C++ program. Often you’re able to
reference C++ variables within the assembly code, which means you can easily communicate with your C++ code and limit the assembly code to that necessary
for efficiency tuning or to use special processor instructions. The exact syntax that you must use when writing the assembly language is compiler-dependent.
- Explicit operators: These are keywords for bitwise and logical operators. Keyword = Meaning: and = && (logical and); or = || (logical or);
not = ! (logical NOT); not_eq = != (logical not-equivalent); bitand = & (bitwise and); and_eq = &= (bitwise and-assignment); bitor = | (bitwise or)
or_eq = |= (bitwise or-assignment); xor = ^ (bitwise exclusive-or); xor_eq = ^=; compl = ~ (ones complement);
- Composite type creation: C++ provide tools that allow you to compose more sophisticated data types from the fundamental data types. The most important
of these is struct, which is the foundation for class in C++. However, the simplest way to create more sophisticated types is simply to alias a name to
another name via typedef.
- Aliasing names with typedef: This keyword promises more than it delivers: typedef suggests “type definition” when “alias” would probably have been a
more accurate description, since that’s what it really does. The syntax is: typedef existing-type-description alias-name. People often use typedef when
data types get slightly complicated, just to prevent extra keystrokes. Ex: typedef unsigned long ulong;
You might think that this could as easily be accomplished using preprocessor substitution, but there are key situations in which the compiler must be
aware that you’re treating a name as if it were a type, so typedef is essential. One place where typedef comes in handy is for pointer types.
If you say: int* x, y; you produce an int* which is x and an int (not an int*) which is y. That is, the ‘*’ binds to the right, not the left.
However, if you use a typedef: typedef int* IntPtr; IntPtr x, y; ,then both x and y are of type int*.
- Combining variables with struct: A struct is a way to collect a group of variables into a structure. Once you create a struct, then you can make many
instances of this “new” type of variable you’ve invented. Ex: struct Structure1 {char c; int i;}; int main() {struct Structure1 s1; s1.c = 'a'; s1.i = 1;}
If you have a pointer to a struct object, you must select an element of that object using a different operator: the ‘->’.
Ex: Structure3* sp = &s1; sp->c = 'a';
- Clarifying programs with enum: An enumerated data type is a way of attaching names to numbers, thereby giving more meaning to anyone reading the code.
The enum keyword automatically enumerates any list of identifiers you give it by assigning them values of 0, 1, 2, etc. The declaration of an enum looks
similar to a struct declaration.
Ex: enum ShapeType {circle=1, square}; int main() {ShapeType shape = circle; switch(shape) {case circle: break; case square: break;}}
If you give a value to the first names (circle) and not to others, the compiler will use the next integral value (square = 1).
- Saving memory with union: Sometimes a program will handle different types of data using the same variable. In this situation, you have two choices:
you can create a struct containing all the possible different types you might need to store, or you can use a union. A union piles all the data into a
single space; it figures out the amount of space necessary for the largest item you’ve put in the union, and makes that the size of the union. Use a
union to save memory. Anytime you place a value in a union, the value always starts in the same place at the beginning of the union, but only uses as
much space as is necessary. Thus, you create a “super-variable” capable of holding any of the union variables. All the addresses of the union variables
are the same (in a class or struct, the addresses are different). It makes no sense to declare more than one instance of a single data type in a union
(unless you’re just doing it to use a different name).
Ex: union Packed {char i; short j;}; int main() {Packed x; x.i = 'c';}
- Arrays are a kind of composite type because they allow you to clump a lot of variables together, one right after the other, under a single identifier
name. If you say: int a[10]; you create storage for 10 int variables stacked on top of each other, but without unique identifier names for each variable.
To access one of these array elements, you use the same squarebracket syntax that you use to define an array: a[5] = 47; However, you must remember that
even though the size of a is 10, you select array elements starting at zero (this is sometimes called zero indexing), so you can select only the array
elements 0-9. You must define the size of the array at compile time; if you want to change the size at runtime you can’t do it. The C++ vector, introduced
in the previous chapter, provides an array-like object that automatically resizes itself, so it is usually a much better solution if your array size
cannot be known at compile time. You can make an array of any type, even of structs.
- Pointers and arrays: The identifier of an array is unlike the identifiers for ordinary variables. For one thing, an array identifier is not an lvalue;
you cannot assign to it. It’s really just a hook into the square-bracket syntax, and when you give the name of an array, without square brackets, what
you get is the starting address of the array. So one way to look at the array identifier is as a read-only pointer to the beginning of an array.
And although we can’t change the array identifier to point somewhere else, we can create another pointer and use that to move around in the array.
Ex: int main() {int a[10]; int* ip = a; for(int i = 0; i < 10; i++) ip[i] = i * 10;} . The fact that naming an array produces its starting address turns
out to be quite important when you want to pass an array to a function. If you declare an array as a function argument, what you’re really declaring is
a pointer.
Arrays can be of any type, including arrays of pointers. In fact, when you want to pass command-line arguments into your program, C++ have a special
argument list for main( ), which looks like this: int main(int argc, char* argv[]) { // ... The first argument is the number of elements in the array,
which is the second argument. The second argument is always an array of char*, because the arguments are passed from the command line as character arrays
(and remember, an array can be passed only as a pointer). Each whitespace-delimited cluster of characters on the command line is turned into a separate
array argument. argv[0] is the path and name of the program itself. This allows the program to discover information about itself.
All you get from the command-line is character arrays; if you want to treat an argument as some other type, you are responsible for converting it
inside your program. To facilitate the conversion to numbers, there are some helper functions in the Standard C library, declared in <cstdlib>.
The simplest ones to use are atoi( ), atol( ), and atof( ) to convert an ASCII character array to an int, long, and double floating-point value,
respectively. Ex: cout << atoi(argv[i]);
- Floating-point format allows C++ to store numbers representing very large and very small values in a limited amount of space. The bits inside of floats
and doubles are divided into three regions: the exponent, the mantissa, and the sign bit; thus it stores the values using scientific notation.
- Pointer arithmetic: If all you could do with a pointer that points at an array is treat it as if it were an alias for that array, pointers into arrays
wouldn’t be very interesting. However, pointers are more flexible than this, since they can be modified to point somewhere else (but remember, the array
identifier cannot be modified to point somewhere else). Pointer arithmetic refers to the application of some of the arithmetic operators to pointers.
The reason pointer arithmetic is a separate subject from ordinary arithmetic is that pointers must conform to special constraints in order to make them
behave properly. For example, a common operator to use with pointers is ++, which “adds one to the pointer.” What this actually means is that the pointer
is changed to move to “the next value,” whatever that means. The compiler figures out the right amount to change the pointer so that it’s pointing to the
next element in the array (pointer arithmetic is only meaningful within arrays). Pointer arithmetic also works with the operators --, +, and -, but the
latter two operators are limited: you cannot add two pointers, and if you subtract pointers the result is the number of elements between the two pointers.
However, you can add or subtract an integral value and a pointer.
- Debugging hints: most debuggers have blind spots, and these will require you to embed code snippets in your program to help you understand what’s going
on.
Preprocessor debugging flags: By using the preprocessor to #define one or more debugging flags (preferably in a header file), you can test a flag using
an #ifdef statement and conditionally include debugging code. When you think your debugging is finished, you can simply #undef the flag(s) and the code
will automatically be removed (and you’ll reduce the size and runtime overhead of your executable file). Preprocessor flags are traditionally
distinguished from variables by writing them in all upper case.
Ex: #define DEBUG // Probably in a header file //... #ifdef DEBUG // Check to see if flag is defined /* debugging code here */ #endif // DEBUG
Most C++ implementations will also let you #define and #undef flags from the compiler command line, so you can recompile code and insert debugging
information with a single command.
Runtime debugging flags: In some situations it is more convenient to turn debugging flags on and off during program execution, especially by setting
them when the program starts up using the command line. To turn debugging code on and off dynamically, create bool flags as in "DynamicDebugFlags.cpp"
Turning variables and expressions into strings: When writing debugging code, it is tedious to write print expressions consisting of a character array
containing the variable name, followed by the variable. When you put a "#" before an argument in a preprocessor macro, the preprocessor turns that
argument into a character array. This, combined with the fact that character arrays with no intervening punctuation are concatenated into a single
character array, allows you to make a very convenient macro for printing the values of variables during debugging.
Ex: #define PR(x) cout << #x " = " << x << "\n"; . This same process works with entire expressions. Ex: PR(a + b); . You can also insert an #ifdef
to cause PR(x) to be defined as “nothing” when you want to strip out debugging.
The C assert( ) macro: In the standard header file <cassert> you’ll find assert(), which is a convenient debugging macro. When you use assert(), you
give it an argument that is an expression you are “asserting to be true.” The preprocessor generates code that will test the assertion. If the assertion
isn’t true, the program will stop after issuing an error message telling you what the assertion was and that it failed. Ex: int i = 100; assert(i != 100);
When you are finished debugging, you can remove the code generated by the macro by placing the line: #define NDEBUG in the program before the inclusion
of <cassert>, or by defining NDEBUG on the compiler command line. NDEBUG is a flag used in <cassert> to change the way code is generated by the macros.
- Function addresses: Once a function is compiled and loaded into the computer to be executed, it occupies a chunk of memory. That memory, and thus the
function, has an address. You can use function addresses with pointers just as you can use variable addresses.
Defining a function pointer: To define a pointer to a function that has no arguments and no return value, you say: void (*funcPtr)();
When you are looking at a complex definition like this, the best way to attack it is to start in the middle and work your way out. “Starting in the middle”
means starting at the variable name, which is funcPtr. “Working your way out” means looking to the right for the nearest item (nothing in this case;
the right parenthesis stops you short), then looking to the left (a pointer denoted by the asterisk), then looking to the right (an empty argument list
indicating a function that takes no arguments), then looking to the left (void, which indicates the function has no return value). This right-left-right
motion works with most declarations. funcPtr is a pointer to a function that takes no arguments and returns void. You may wonder why *funcPtr requires
parentheses. If you didn't use them, the compiler would see: void *funcPtr(); . You would be declaring a function (that returns a void*) rather than
defining a variable.
Complicated declarations & definitions: once you figure out how the C++ declaration syntax works you can create much more complicated items. For
instance: void * (*(*fp1)(int))[10]; - “fp1 is a pointer to a function that takes an integer argument and returns a pointer to an array of 10 void
pointers.” . float (*(*fp2)(int,int,float))(int); says “fp2 is a pointer to a function that takes three arguments (int, int, and float) and returns a
pointer to a function that takes an integer argument and returns a float.”
If you are creating a lot of complicated definitions, you might want to use a typedef. A typedef saves typing the complicated description every time.
Ex: typedef double (*(*(*fp3)())[10])(); fp3 a; It says “An fp3 is a pointer to a function that takes no arguments and returns a pointer to an array
of 10 pointers to functions that take no arguments and return doubles.” Then it says “a is one of these fp3 types.” typedef is generally useful for
building complicated descriptions from simple ones. int (*(*f4())[10])(); is a function declaration instead of a variable definition. It says “f4 is a
function that returns a pointer to an array of 10 pointers to functions that return integers.” You will rarely if ever need such complicated declarations
and definitions as these.
Using a function pointer: Once you define a pointer to a function, you must assign it to a function address before you can use it. Just as the
address of an array arr[10] is produced by the array name without the brackets (arr), the address of a function func() is produced by the function name
without the argument list (func). You can also use the more explicit syntax &func(). To call the function, you dereference the pointer in the same way
that you declared it. Ex: void func(){} int main() {void (*fp)();// Define a function pointer fp = func;//Initialize it (*fp)();//Dereferencing calls
the function
Arrays of pointers to functions: One of the most interesting constructs you can create is an array of pointers to functions. To select a function,
you just index into the array and dereference the pointer. This supports the concept of table-driven code; instead of using conditionals or case
statements, you select functions to execute based on a state variable. This kind of design can be useful if you often add or delete functions from the
table (or if you want to create or change such a table dynamically). See FunctionTable.cpp.
- Make: managing separate compilation: When using separate compilation (breaking code into a number of translation units), you need some way to
automatically compile each file and to tell the linker to build all the pieces – along with the appropriate libraries and startup code – into an
executable file. Most compilers allow you to do this with a single command-line statement. For the GNU C++ compiler, for example, you might say
g++ SourceFile1.cpp SourceFile2.cpp. The problem with this approach is that the compiler will first compile each individual file, regardless of whether
that file needs to be rebuilt or not. With many files in a project, it can become prohibitive to recompile everything if you’ve changed only a single file.
The solution to this problem, developed on Unix but available everywhere in some form, is a program called make. The make utility manages all the
individual files in a project by following the instructions in a text file called a makefile. When you edit some of the files in a project and type make,
the make program follows the guidelines in the makefile to compare the dates on the source code files to the dates on the corresponding target files, and
if a source code file date is more recent than its target file, make invokes the compiler on the source code file. make only recompiles the source code
files that were changed, and any other source-code files that are affected by the modified files.
Make activities: When you type make, the make program looks in the current directory for a file named makefile, which you’ve created if it’s your
project. This file lists dependencies between source code files. make looks at the dates on files. If a dependent file has an older date than a file it
depends on, make executes the rule given after the dependency. All comments in makefiles start with a # and continue to the end of the line.
Ex: # A comment hello.exe: hello.cpp mycompiler hello.cpp . This says that hello.exe (the target) depends on hello.cpp. When hello.cpp has a newer date
than hello.exe, make executes the “rule” mycompiler hello.cpp. There may be multiple dependencies and multiple rules. Many make programs require that all
the rules begin with a tab. Other than that, whitespace is generally ignored so you can format for readability.
Macros: A makefile may contain macros. Macros allow convenient string replacement. Ex: CPP = mycompiler hello.exe: hello.cpp $(CPP) hello.cpp .
The = is used to identify CPP as a macro, and the $ and parentheses expand the macro. With the macro above, if you want to change to a different compiler
called cpp, you just change the macro to: CPP = cpp.
Suffix Rules: A suffix rule is the way to teach make how to convert a file with one type of extension (.cpp, for example) into a file with another
type of extension (.obj or .exe). The suffix rule tells make that it doesn’t need explicit rules to build everything, but instead it can figure out how to
build things based on their file extension. Ex: CPP = mycompiler .SUFFIXES: .exe .cpp .cpp.exe: $(CPP) $< . The .SUFFIXES directive tells make that it
should watch out for any of the following file-name extensions because they have special meaning for this particular makefile. Next you see the suffix
rule .cpp.exe, which says “Here’s how to convert any file with an extension of cpp to one with an extension of exe” (when the cpp file is more recent
than the exe file). As before, the $(CPP) macro is used, but then you see something new: $<. Because this begins with a ‘$’ it’s a macro, but this is one
of make’s special built-in macros. The $< can be used only in suffix rules, and it means “whatever prerequisite triggered the rule” (sometimes called the
dependent), which in this case translates to “the cpp file that needs to be compiled.”. Once the suffix rules have been set up, you can simply say, for
example, “make Union.exe,” and the suffix rule will kick in, even though there’s no mention of “Union” anywhere in the makefile.
Default targets: After the macros and suffix rules, make looks for the first “target” in a file, and builds that, unless you specify differently.
For the following makefile: CPP = mycompiler .SUFFIXES: .exe .cpp .cpp.exe: $(CPP) $< target1.exe: target2.exe: . If you just type ‘make’, then
target1.exe will be built (using the default suffix rule) because that’s the first target that make encounters. To build target2.exe you’d have to
explicitly say ‘make target2.exe’. This becomes tedious, so you normally create a default “dummy” target that depends on all the rest of the targets,
like this: CPP = mycompiler .SUFFIXES: .exe .cpp .cpp.exe: $(CPP) $< all: target1.exe target2.exe . Here, ‘all’ does not exist and there’s no file
called ‘all’, so every time you type make, the program sees ‘all’ as the first target in the list (and thus the default target), then it sees that ‘all’
does not exist so it had better make it by checking all the dependencies. So it looks at target1.exe and (using the suffix rule) sees whether (1)
target1.exe exists and (2) whether target1.cpp is more recent than target1.exe, and if so runs the suffix rule (if you provide an explicit rule for a
particular target, that rule is used instead). Then it moves on to the next file in the default target list. Thus, by creating a default target list
(typically called ‘all’ by convention, but you can call it anything) you can cause every executable in your project to be made simply by typing ‘make’.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Cap4: Data Abstraction
- the most minimal package is a library (a file with the extension lib) and one or more header files to tell your compiler what’s in the library.
- A tiny C-like library: most C libraries have a set of structs and a set of functions that act on those structs. A linked list = each element in
your list contains a pointer to the next element.
- The heap is a big block of memory used for allocating smaller pieces at runtime. You use the heap when you don’t know the size of the memory you’ll
need while you’re writing a program.C++ has a sophisticated approach to dynamic memory that is integrated into the language via the keywords new and
delete. The general form of the new-expression is: new Type; in which Type describes the type of variable you want allocated on the heap.
Ex: new unsigned char[newBytes];
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
15.06: 255 -
31.06: 256 - 259;
30.06: Ex: 25 - 32;
29.06: Ex: 9 - 24
26.06: Ex: 1 - 8
25.05: 233 - 247
24.05: 205 - 233
22.05: 174 - 205
Legenda:
OOP = object oriented programming
The static storage area = is simply a fixed patch of memory that is allocated before the program begins to run.
The stack = is an area in memory that is used directly by the microprocessor to store data during program execution. Variables on the stack are sometimes
called automatic or scoped variables.
Heap = a pool of memory
class = a user-defined data type. Classes that someone else has created are typically packaged into a library.
IEEE = Institute of Electrical and Electronics Engineers - IEEE is best known for developing standards for the computer and electronics industry.
Probleme:
- nu inteleg: C03:Rotation.cpp (pg 196)