[笔记]C++笔记(2)

CH8.1 Classes

Use the struct keyword for data-only structures. Use the class keyword for objects that have both data and functions. (struct will cause potential memory leaks)

Data in struct are public by default. As an opposite, data in class are private by default. Member functions in class are public by default.

Make member variables private, and member functions public, unless you have a good reason not to.

Access control works on a per-class basis, not a per-object basis: when a function has access to the private members of a class, it can access the private members of any object of that class type that it can see.

See the following example:

#include <iostream>
 
class DateClass // members are private by default
{
	int m_month; // private by default, can only be accessed by other members
	int m_day; // private by default, can only be accessed by other members
	int m_year; // private by default, can only be accessed by other members
 
public:
	void setDate(int month, int day, int year)
	{
		m_month = month;
		m_day = day;
		m_year = year;
	}
 
	void print()
	{
		std::cout << m_month << "/" << m_day << "/" << m_year;
	}
 
	// Note the addition of this function
	void copyFrom(const DateClass &d)
	{
		// Note that we can access the private members of d directly
		m_month = d.m_month;
		m_day = d.m_day;
		m_year = d.m_year;
	}
};
 
int main()
{
	DateClass date;
	date.setDate(10, 14, 2020); // okay, because setDate() is public
	
	DateClass copy;
	copy.copyFrom(date); // okay, because copyFrom() is public
	copy.print();
 
	return 0;
}

Only provide access functions when it makes sense for the user to be able to get or set a value directly.

Getters should usually return by value or const reference, not non-const reference


CH8.4 Constructors, destructors and this

Direct initialization and uniform initialization (C++11)

int x(5); // Direct initialize an integer
Fraction fiveThirds(5, 3); // Direct initialize a Fraction, calls Fraction(int, int) constructor

int x { 5 }; // Uniform initialization of an integer
Fraction fiveThirds {5, 3}; // Uniform initialization of a Fraction, calls Fraction(int, int) constructor

Do not copy initialize your classes. (explained in the future)

Default constructor is only created automatically if no DEFAULT constructor exists.

Member initializer lists
class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
// If you assign values to them, in C++11, this will become default initialization for all constructors.

public:
    Something() : m_value1(1), m_value2(2.2), m_value3('c') // directly initialize our member variables
    {
    // No need for assignment here
    }
    
or
    public:
    Something(int value1, double value2, char value3='c')
        : m_value1(value1), m_value2(value2), m_value3(value3) // directly initialize our member variables
    {
    // No need for assignment here
    }
    
 
    void print()
    {
         std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
    }
};
 
int main()
{
    Something something;
    something.print();
    return 0;
}

Initialize at class construction.

In C++11, use uniform initialization.

class Something
{
private:
    const int m_value;
 
public:
    Something(): m_value { 5 } // Uniformly initialize our member variables
    {
    } 
};

Favor uniform initialization over direct initialization if you compiler is C++11 compatible

Favor use of non-static member initialization to give default values for your member variables.

Delegating constructors
#include <string>
#include <iostream>
 
class Employee
{
private:
    int m_id;
    std::string m_name;
 
public:
    Employee(int id=0, std::string name=""):
        m_id(id), m_name(name)
    {
        std::cout << "Employee " << m_name << " created.\n";
    }
 
    // Use a delegating constructors to minimize redundant code
    Employee(std::string name) : Employee(0, name) { }
};

exit() doesn't call destructors.

this can be used to chain objects together:

class Calc
{
private:
    int m_value;
 
public:
    Calc() { m_value = 0; }
 
    Calc& add(int value) { m_value += value; return *this; }
    Calc& sub(int value) { m_value -= value; return *this; }
    Calc& mult(int value) { m_value *= value; return *this; }
 
    int getValue() { return m_value; }
};

Then, do

#include <iostream>
int main()
{
    Calc calc;
    calc.add(5).sub(3).mult(4);
 
    std::cout << calc.getValue() << '\n';
    return 0;
}

CH8.6 Member functions and friend

Make any member function that does not modify the state of the class object const

class Something // If const this class, then getValue has to be set to const
{
public:
    int m_value;
 
    Something() { m_value= 0; } // Constructor cannot be const!
 
    void resetValue() { m_value = 0; }
    void setValue(int value) { m_value = value; }
 
    int getValue() const; // note addition of const keyword here
};
 
int Something::getValue() const // and here
{
    return m_value;
}

Because passing objects by const reference is common, your classes should be const-friendly. That means making any member function that does not modify the state of the class object const!

static member variables are shared by all instantiations of the class. The static member variables are initialized BEFORE any instantiation of the class (they get initialized when the program starts):

class Something
{
public:
    static int s_value; // declares the static member variable. 
};
 
int Something::s_value = 1; // defines the static member variable, MUST BE DONE BEFORE ANY ATTEMPT TO INSTANTIATE THE CLASS (or C++ will do for you, set to default values), except for the case where object is const.
 
int main()
{
    // note: we're not instantiating any objects of type Something
 
    Something::s_value = 2;
    std::cout << Something::s_value << '\n';
    return 0;
}

Note: The static object DEFINITION doesn't subject to private/protected access control. (You can still initialize protected/private static object outside the class)

Put static object in .cpp file not header file will prevent potential multiple definitions (if the header file is included multiple times).

Static functions can be used to manipulate static variables without instantiating the class.

C++ doesn't have static constructor, this is a way to do that:

class MyClass
{
private:
	static std::vector<char> s_mychars;
 
public:
 
	class _init // we're defining a nested class named _init
	{
	public:
		_init() // the _init constructor will initialize our static variable
		{
			s_mychars.push_back('a');
			s_mychars.push_back('e');
			s_mychars.push_back('i');
			s_mychars.push_back('o');
			s_mychars.push_back('u');
		}
	} ;
 
private:
	static _init s_initializer; // we'll use this static object to ensure the _init constructor is called
};
 
std::vector<char> MyClass::s_mychars; // define our static member variable
MyClass::_init MyClass::s_initializer; // define our static initializer, which will call the _init constructor, which will initialize s_mychars

Because friend functions do not have this pointer (it is not a member function), you should pass by reference:

class Value
{
private:
    int m_value;
public:
    Value(int value) { m_value = value; }
    friend bool isEqual(const Value &value1, const Value &value2);
};
 
bool isEqual(const Value &value1, const Value &value2)
{
    return (value1.m_value == value2.m_value);
}

Declare class prototype in advance if needed.

Making a class a friend only requires as forward declaration that the class exists. However, making a specific member function a friend requires the full declaration for the class of the member function to have been seen first.

Friend classes do not have access to friend's this pointer. Friendship is also not "transferrable".


CH9.1 Operator overloading
  1. Overloading cannot be defined if operands are all fundamental types.
  2. Overloaded operator has same precedence as its un-overloaded counterpart.

3 ways for overloading: friend function, member function, normal function.

The reverse order of overloading can be defined by calling previously defined overloading.

Prefer overloading operators as normal functions instead of friends if it’s possible to do so without adding additional functions.

Overload <<:

std::ostream& operator<< (std::ostream &out, const Class &_class);

The reason for using &: returned value must still exist when the called function returns.

An example for overloading << and >>:

#include <iostream>
 
class Point
{
private:
    double m_x, m_y, m_z;
 
public:
    Point(double x=0.0, double y=0.0, double z=0.0): m_x(x), m_y(y), m_z(z)
    {
    }
 
    friend std::ostream& operator<< (std::ostream &out, const Point &point);
    friend std::istream& operator>> (std::istream &in, Point &point);
};
 
std::ostream& operator<< (std::ostream &out, const Point &point)
{
    // Since operator<< is a friend of the Point class, we can access Point's members directly.
    out << "Point(" << point.m_x << ", " << point.m_y << ", " << point.m_z << ")";
 	// must return reference so it still exists after function call ends.
    return out;
}
 
std::istream& operator>> (std::istream &in, Point &point)
{
    // Since operator>> is a friend of the Point class, we can access Point's members directly.
    // note that parameter point must be non-const so we can modify the class members with the input values
    in >> point.m_x;
    in >> point.m_y;
    in >> point.m_z;
 
    return in;
}

Overloading as member functions or friend functions only differ slightly (the "leftmost" parameter in overloading member function now is replaced with this, thus could be omitted). However:

The assignment (=), subscript ([]), function call (()), and member selection (->) operators must be overloaded as member functions, because the language requires them to be.

*Because overloading as member function, the overloaded operator must be added as a member of the left operand, so overload << as member function would fail. * (friend functions are NOT member functions. The compiler translates this to left_operand.operator_overloaded(right_operand))

  • If you’re overloading assignment (=), subscript ([]), function call (()), or member selection (->), do so as a member function.
  • If you’re overloading a unary operator (!,+,-), do so as a member function.
  • If you’re overloading a binary operator that modifies its left operand (e.g. operator+=), do so as a member function if you can. (Make the leftmost operand as class object, and the rightmost operand becomes an explicit parameter, the left operand, which is essentially this, is always omitted.)
  • If you’re overloading a binary operator that does not modify its left operand (e.g. operator+), do so as a normal function or friend function. (Because the left operand can be not a class or not modifiable)
  1. Overload ++/--:

Distinguish from post/prefix operator:

int& operator++()         // Prefix Increment
{
	return (*this)+1;
}

int operator++(int)
{
	int temp;
	*this = (*this)+ 1;
	return temp;
}
//Postfix Increment, that's the reason: ++, then return PREVIOUS value

// SO POSTFIX IS GENERALLY INEFFICIENT THAN PREFIX COUNTERPART
  1. Overload []:
class IntList
{
private:
    int m_list[10];
 
public:
    int& operator[] (const int index);
};
 
int& IntList::operator[] (const int index)
{
    return m_list[index];
} // int& is lvalue. But if int is returned, it will evaluate to numa=numb, error
  1. Overload ():
#include <cassert> // for assert()
class Matrix
{
private:
    double data[4][4];
public:
    Matrix()
    {
        // Set all elements of the matrix to 0.0
        for (int row=0; row < 4; ++row)
            for (int col=0; col < 4; ++col)
                data[row][col] = 0.0;
    }
 
    double& operator()(int row, int col);
    const double& operator()(int row, int col) const; // for const objects
};
 
double& Matrix::operator()(int row, int col)
{
    assert(col >= 0 && col < 4);
    assert(row >= 0 && row < 4);
 
    return data[row][col];
}
 
const double& Matrix::operator()(int row, int col) const
{
    assert(col >= 0 && col < 4);
    assert(row >= 0 && row < 4);
 
    return data[row][col];
}

() can also be used to create functors that are essentially classes but can storage values and have multiple instances. An example:

class Accumulator
{
private:
    int m_counter = 0;
 
public:
    Accumulator()
    {
    }
 
    int operator() (int i) { return (m_counter += i); }
};
 
int main()
{
    Accumulator acc;
    std::cout << acc(10) << std::endl; // prints 10
    std::cout << acc(20) << std::endl; // prints 30
 
    return 0;
}
  1. Overload typecasts:
class Cents
{
private:
    int m_cents;
public:
    Cents(int cents=0)
    {
        m_cents = cents;
    }
 
    // Overloaded int cast
    operator int() { return m_cents; }
 // Cast Cents to int, C++ assumes you return the right value.
    int getCents() { return m_cents; }
    void setCents(int cents) { m_cents = cents; }
};

void printInt(int value)
{
    cout << value;
}

int main()
{
    Cents cents(7);
    printInt(cents); // print 7
// or just std::cout<<cents;
    return 0;
}
  1. Overload = (assignment operator):

Difference between copy and assign:

  • If a new object has to be created before the copying can occur, the copy constructor is used (note: this includes passing or returning objects by value).
  • If a new object does not have to be created before the copying can occur, the assignment operator is used.
CH9.11 Copy initialization, shallow and deep copying

Recap:

int x(5); // Direct initialize an integer
Fraction fiveThirds(5, 3); // Direct initialize a Fraction, calls Fraction(int, int) constructor

int x { 5 }; // Uniform initialization of an integer
Fraction fiveThirds {5, 3}; // Uniform initialization of a Fraction, calls Fraction(int, int) constructor

int x = 6; // Copy initialize an integer
Fraction six = Fraction(6); // Copy initialize a Fraction, will call Fraction(6, 1)
Fraction seven = 7; // Copy initialize a Fraction.  The compiler will try to find a way to convert 7 to a Fraction, which will invoke the Fraction(7, 1) constructor.
  1. Copy constructor:
class Fraction
{
private:
    int m_numerator;
    int m_denominator;
 
public:
    // Default constructor
    Fraction(int numerator=0, int denominator=1) :
        m_numerator(numerator), m_denominator(denominator)
    {
        assert(denominator != 0);
    }
 
    // Copy constructor
    Fraction(const Fraction &fraction) :
        m_numerator(fraction.m_numerator), m_denominator(fraction.m_denominator)
        // Note: We can access the members of parameter fraction directly, because we're inside the Fraction class
    {
        // no need to check for a denominator of 0 here since fraction must already be a valid Fraction
    }
};

Remember,  In C++ access control works on per-class basis, not on per-object basis.

Make the copy constructor private to prevent from copying. This private constructor is always triggered when it meets matching calls. The compiler would not opt out this.

For example:

Fraction fiveThirds(Fraction(5, 3));
//The compiler may change this to:
Fraction fiveThirds(5, 3);// This doesn't call copy constructor UNLESS it is private. (Leads to compilation error)

Avoid using copy initialization intentionally, and use uniform initialization instead.

Typically variables are returned by value, in this case copy initializer is called by process. But the compiler may opt out this:

class Something
{
};
 
Something foo() 
{
  Something s;
  return s;
}
 
int main()
{
  Something s = foo();
}
  1. Converting constructors:
// suppose we have constructor:
Fraction(int numerator=0, int denominator=1) :
        m_numerator(numerator), m_denominator(denominator)
        
// we can (in C++11) pass an integer to this class. It will be converted to a "Fraction"

Constructors decorated by explicit do not allow implicit conversion.

Consider making your constructors explicit to prevent implicit conversion errors

  1. Delete constructor:
someclass(type) = delete; // Any attempt to use this constructor will result a compile error

The copy constructor and overloaded operators may also be deleted in order to prevent those functions from being used.

  1. Shallow and deep copying

Shallow copying ONLY copy address and can cause problem, like the following case:

// The default copy constructor
MyString::MyString(const MyString &source) :
    m_length(source.m_length), m_data(source.m_data)
{
}

// Then called in main:
int main()
{
    MyString hello("Hello, world!");
    {
        MyString copy = hello; // use default copy constructor
    } // copy gets destroyed here
 
    std::cout << hello.getString() << '\n'; // this will have undefined behavior
 
    return 0;
}

Solution: deep copying

// Copy constructor (Deep copying)
MyString::MyString(const MyString& source)
{
    // because m_length is not a pointer, we can shallow copy it
    m_length = source.m_length;
 
    // m_data is a pointer, so we need to deep copy it if it is non-null
    if (source.m_data)
    {
        // allocate memory for our copy
        m_data = new char[m_length];
 
        // do the copy
        for (int i=0; i < m_length; ++i)
            m_data[i] = source.m_data[i];
    }
    else
        m_data = 0;
}

// Overload assignment operator "="
MyString& MyString::operator=(const MyString & source)
{
    // check for self-assignment
    if (this == &source)
        return *this;
 
    // first we need to deallocate any value that this string is holding!
    delete[] m_data;
 
    // because m_length is not a pointer, we can shallow copy it
    m_length = source.m_length;
 
    // m_data is a pointer, so we need to deep copy it if it is non-null
    if (source.m_data)
    {
        // allocate memory for our copy
        m_data = new char[m_length];
 
        // do the copy
        for (int i=0; i < m_length; ++i)
            m_data[i] = source.m_data[i];
    }
    else
        m_data = 0;
 
    return *this;
}

Classes in the standard library that deal with dynamic memory, such as std::string and std::vector, do proper shallow/deep copying and proper = overloading.

  • The default copy constructor and default assignment operators do shallow copies, which is fine for classes that contain no dynamically allocated variables.
  • Classes with dynamically allocated variables need to have a copy constructor and assignment operator that do a deep copy.
  • Favor using classes in the standard library over doing your own memory management.

posted on 2017-02-16 09:39  小硕鼠  阅读(181)  评论(0)    收藏  举报

导航