Skip to main content

Transition from C to C++

 

Transition to C++

3 Versions of C++

C++ 03,
C++ 11,
C++ 14,
C++ 17, etc.


Object-Oriented Programming (OOP)

- One core difference between C and C++ is that C++ supports OOD. In OOP, we think about our program in terms of objects, and we ask the objects to perform actions. The actual implementation of how to perform action is hidden from us. 

- Two primary concepts in OOP are classes and objects. 

- The only difference in C++ between a class and a struct is the default access control of its members.

- In C++, classes and structs can have functions declared inside of them. These functions are either called member functions or methods.

e.g.
class myClass {
    int a;
    int b;
    int f (int x) {
        return x * a - b;
    }
}

Access Control

- In C++, members of a struct or a class can have their access restricted so that only code within the class can directly access them.

- There are three levels of access (also called visibility) that each member can have: public private or protected.

- PUBLIC: Can be accessed by any piece of code

- PRIVATE: Can be accessed only by code within that class

- PROTECTED: Will be discussed after discussing inheritance. 

- In class, the default access is private. Therefore, only the public class can be accessed by any piece of code. But for struct, the default access is public. 

Encapsulation

- For each method, there' ll be a default 'this' inside the function.

Const Methods

- When methods are created, the type 'this' is T * const this. But if we want to have const T * const this, we can add a const after the parentheses but before the curly brace. 
e.g. 
int getX () const {
    return x;
}

- It's important because we can invoke a const method upon a const object but cannot invoke a non-const method upon a const object. 

e.g. 
void someMethod(const Point * p) {
    int x = p->getX();
    p->setX(42);
}

- Here, getX() is ok. But since setX() is not a non-const method, it is illegal because const declaration ensures we  will not modify the contents of the object.

Plain Old Data (POD)

- C++ makes a distinction between classes (or structs) that are plain old data (POD) and classes that are not. 

- All POD types have direct C analogs - they are sequences of fields in memory that we can copy around freely. 

- If we can write in C, it is POD. If we cannot write in C, then it is probably not POD with one exception: declaring functions inside a class or struct, they do not make the type non-POD unless they are virtual functions.

- Functions are actually not inside the object. It resides in the memory and only the 'this' pointer tells where the object is. Objects have a direct C analog.
e.g. 
class Myclass {
public:
    int x;
    int y;
    int getSum() const {
        return x + y;
    }
}

we could also write in this way:
typedef struct {
    int x;
    int y;
} MyClass;

int getSum (const MyClass * const this) {
    return this->x + this->y;
}

- sizeof(Myclass) would be the same whether we did C++ version or C version. And the offset of each field relative to the start of the object would be the same. 

- Visiting restrictions (private) or mixing multiple visibilities (mix public with private) in a class makes it non-POD.

- For non-POD, we cannot work on the "raw" memory directly in a safe way like allocating, copying or deallocating that memory. We need to use the C++ operators which know how to work with non-POD types properly.

Static Members

- We may want relationship between different bankAccount. i.e. There may be a shared box for a particular field. We may want to know the account number. Meanwhile, we also want to know what next account number is.

- Under this situation, we can use the keyword static. It means there is one box shared by all instances of the bakaccount class, not one box per instance
e.g.

class Bankacccount {
private:
    static unsigned long nextAccountNumber;
    unsigned long accountNumber;
    double balance;
public:
    void initAccount() {
        accountNumber = nextAccountNumber;
        nextAccountNumber++;
    balance = 0;
    }
}

- BUT,  static unsigned long nextAccountNumber; does not actually create this box. We still need one line of code outside of the class definition.
i.e.
unsigned long BankAccount::nextAccountNumber = 0;

- The above line of code actually creates the box and must be placed at the global scope - outside of any functions or classes. 

- :: is the scope resolution operator, which allows us to specify a name inside of another named scope such as class.

- This box should be created before the main begins.

- We can also write methods in a class, but these methods cannot access non-static members because they don't have a 'this' pointer passed to them.


Classes Can Contain Other Types

-  Classes can contain other types, including a typedef or even the declaration of an entire other class.

- We should set private for inner class.

- Inner class doesn't have direct access to non-static members of the outer class. But we can have a field in the inner class which is a pointer to the outer class type and initializing it to point at the appropriate outer-class instance.


Good OO  Design

- Classes are nouns. Methods are verbs.

- Keep classes small and to their purposes.

- Be general as you can be, but no more

- Avoid "Manager" classes


References

- In C++, instead of *, we use & to represent a reference.


- When passing a reference, we use, e.g. swap(&a, &b) to pass reference

- But we implicitly dereference the reference. And we don't add & before a value to get its address.
i.e.
a = *a in pointer

- const reference may be initialized from something that is not an lvalue. BUT it is only legal when the reference is constant.
e.g.
void afunction(const int & x) {
   //...
}

void bfunction() {
    afunction(3);
}

- The above function is legal because x is a const int &. But if it is int &, it will be illegal.

- What actually happens is that compiler creates a temporary const in variable and passes the address of that variable. 

- Reference and pointer are different types although they have similarities. 

- We can define an operator that operates on two references but not on two pointers. 

Namespaces

- In C++, functions and type names reside in a global scope and is visible throughout the entire program. But we can restrict the visibility of a function's name by declaring it as static

- If a function is not declared static, then it must be unique in the entire program.

- If it is declared static, then it may not be used in any other compilation unit. But it introduces the problem of name collisions. 

- C++ introduces a way to create named scopes, called namespaces. It can be used from anywhere in the program. 

- Declaration can be placed inside of a namespace.
e.g.
namespace dataAnalysis {
    class DataPoint {...};
    class DataSet {...};
    DataSet combineDataSets (DataSet * array, int n);
}

- 2 ways to reference a name declared inside a namespace. 
    1) scope resolution operator ::
            If we want to use the vector class in the C++ standard library, which is in the std namespace, we can reference it by its fully qualified name:
std::vector.
    2) open the namespace with the using keyword. It instructs the compiler to bring the names from the requested namespace into the current scope. e.g.:
using namespace std;
    and then, we can just use vector to refer to std::vector

- using std::vector instead of using namespace std is typically preferable

Function Overloading

- Allowing multiple functions of the same name is called function overloading.

- It is legal if and only if this function can be distinguished by their parameter types. Differed in return type and parameter names are not a valid overloading. 

- When using overload, we should follow the following guideline:
    1) They should perform the same task but different types.
    2) We should only overload functions in such a way that understanding what the best choice is for a particular call is straightforward.

Name Mangling

- Since in the process of C++ compiler, the names of the symbols the linker sees should be unique, it performs a name mangling to adjust the function names to encode the parameter type in formation, as well as what class and name

- In C, it does not mangle names. If we mix C and C++ code, then C++ compiler must be explicitly informed using extern "C", e.g.
extern "C" {
void someFunction();
int anotherFunciton();
}

- main function is treated specially as if it were declared extern "C" as it may be declared with or without parameters for the command line arguments and is called by the startup library, which is frequently written in C.

Operator Overloading

- C++ takes function overloading one step further, allowing operator overloading.
e.g.
class Point {
private:
    int x;
    int y;
public:
    Point operator+(const Point & rhs) const {
        Point ans;
        ans.x = x + rhs.x;
        ans.y = y + rhs.y;
        return ans;
}
}

- We can write function declaration that defines the behavior of the operators when at least one user-defined type (e.g. class) is involved. 
e.g.
Matrix & operator + (const Matrix & rhs) {
    //...
}

- Here we define an overloading of the + operator inside of the point class. It takes 2 points as arguments. The first point is 'this'. The second operand is a const reference.

- In the case of operators that modify the object they are inside of, such as +=, they return a reference to that object. i.e. their return type is a reference to their own class type, and they return *this.
e.g.
Matrix & operator += (const Matrix & rhs) {
    x += rhs.x;
    y += rhs.y;
    return *this;
}

-When returning *this, a reference is being initialized. Thus, the address is implicitly taken. i.e. the pointer is &*this, which is just 'this'.

- The reason why it returns the object is that a = b += c is legal. 


Bool

- We should use bool for the type of parameters and variables that represent a boolean value.

Void Pointers

- In C, we can assign any pointer type to a void pointer and assign a void pointer to any pointer type without a cast. However, in C++ we need to cast explicitly.

Standard Headers

- In C, the header file ends with .h, but in C++, it is just, e.g. <vector>

- The scope of header file is global, it's not a preferred way to include the C standard library. Instead, C++ provides a header file that has the same name as the C header file but starts with a c and does not end with .h.
e.g.
C++ vs C
<cstdlib> vs <stdlib.h>
<cstdio> vs <stdio.h>

Code Organization

- In header file bank.h, we write the declaration, e.g.
class BankAccount {
private:
    double balance;
public:
    void deposit(double amount);
    double withdraw(double desiredAmount);
    double getBalance() const;
    void initAccount();
}

- In corresponding cpp file, we would write:
#include "bank.h"
void BankAccount::deposit(double amount) {
    balance += amount;
}

double BankAccount::withdraw(double desiredAmount) {
    if (desiredAmount <= balance) {
        balance -= desiredAmount;
        return desiredAmount;
} else {
        double actualAmount = balance;
        balance = 0;
        return actualAmount;
    }
}

double BankAccount::getBalance() {
    return balance;
}

void BankAccount::initAccount() {
    balance = 0;
}

Default Values

- C++ allows default values. 
e.g.
int f(int x, int y = 3, int z = 4, bool b = false);

- We should not abuse default values!! Only when we really need them.

- If we want to use them, we should declare default values in the header file. If not, we cannot use them even we declares them in the implementation. 

nullptr

- In C, and C++03, we always use NULL to represent null pointer. However, it is not safe.

- C++11 improves on the definition of NULL by providing nullptr. It is of type nullptr_t. This is compatible with any other pointer type. (We can assign it to any pointer type, or compare a pointer of type nullptr_t to any other pointer) However, it is not compatible with int. 

-Expression of type nullptr_t may still be implicitly converted to boolean.

summary& understanding  after reading <<All of Programming>> Chapter 14 & Appendix E6.1

Comments

Popular posts from this blog

Templates

  Template - Polymorphism is the ability of the same code to operate on different types. This ability to operate on multiple types reduces code duplication by allowing the same piece of code to be reused across the different types it can operate on. - Polymorphism comes in a variety of forms. What we are interested in at the moment is parametric polymorphism, meaning that we can write our code so that it is parameterized over what type it operates on.  -That is, we want to declare a type parameter T and replace int with T in the above code. -Then, when we want to call the function, we can specify the type for T and get the function we desire. C++ provides parametric polymorphism through templates. Templated Functions - We can write a templated function by using the keyword template followed by the template parameters in angle brackets (<>). - Unlike function parameters, template parameters may be types, which are specified with typename where the type of the parameter wo...

useMemo的使用场景

 useMemo是用来缓存 计算属性 的。 计算属性是函数的返回值,或者说那些以返回一个值为目标的函数。 有些函数会需要我们手动去点击,有些函数是直接在渲染的时候就执行,在DOM区域被当作属性值一样去使用。后者被称为计算属性。 e.g. const Component = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 = () => { console . log ( "我需要手动调用,你不点击我不执行" ); setParams1 (( val ) => val + 1 ); }; //这种被称为计算属性,不需要手动调用,在渲染阶段就会执行的。 const computedFun2 = () => { console . log ( "我又执行计算了" ); return params2 ; }; return ( < div onClick = { handleFun1 } > //每次重新渲染的时候我就会执行 computed: { computedFun2 () } </ div > ); }; 上面的代码中,在每次点击div的时候,因为setParams1的缘故,导致params1改变,整个组件都需要重新渲染,也导致comptedFunc2()也需要重新计算。如果computedFunc2的计算量很大,这时候重新计算会比较浪费。 可以使用useMemo: const Com = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 ...

Valgrind

  Using Valgrind to check memory How to use Valgrind -To valgrind the program, run the valgrind command and give it our program name as an argument. -For example, if we want to run ./myProgram hello 42, we can simply run Valgrind ./myProgram hello 42.  Uninitialized Values -When we run the program, we may use uninitialized values. It needs to be fixed. Valgrind can tell us about the use of uninitialized values. But it only tell when the control flow of the program depends on the unitialized value. For example, uninitialized value appears in the conditional expression of an if, or a loop, or in the switch statement. -If we want to know where the uninitialized value is from, we can use Valgrind    --track-origins=yes ./myProgram -Using -fsanitize=address can find a lot of problems that Memcheck cannot.  -We can use Valgrind with GDB to debug. We can run: --vgdb=full --vgdb-error=0., then Valgrind will stop on the first error that it encounters and give control to ...