Copy Ctor & Overloaded Operators | 拷贝构造与重载运算符👀
约 1711 个字 212 行代码 预计阅读时间 8 分钟
Copy Ctor | 拷贝构造👀
一道题
For the code below
which statement is RIGHT for the line in the function
f()
?1. This is a variable definition, while students is an object of Stash, initialized with the default constructor.
2. This is a function prototype, while students is a function returns an object of Stash.
3. This is a function call.
4. This is illegal in C++.
- false : 构造对象如果没有参数,不能加括号
- true : 函数原型声明是允许在函数内部发生的
- false
- false
-
Copying
- Create a new object from an existing one | 在 C 中与传数组不同,函数的参数如果是结构体会在函数内部复制整个结构,而数组是指针 — C++ 也是这样
// Currency as pass-by-value argument void func(Currency c) { // ... } ··· Currency cur(10, 50); func(cur); // cur is copied to c
- 此时 func 中的 c 是 cur 的拷贝,在调用
func
的时候会调用拷贝构造函数
The copy constructor👀
- Copying is implemented by the copy constructor
- Has the unique signature
ClassName::ClassName(const ClassName &)
- Call-by-reference is used for the explicit parameter
- C++ builds a copy ctor for you if you don’t provide one
- Copies each member variable — Good for numbers, objects(会递归调用对象中的拷贝构造函数), arrays
- Copies each pointer – 源对象和拷贝构造对象指针相同,指向同一片内存区域
- 当不需要全盘拷贝 or 对象中有指针,需要自己写 Copy Ctor
延申
-
When are Copy Ctor called
- During call by value
- During initialization
Currency cur(10, 50); Currency cur2 = cur; // cur is copied to cur2 Currency cur3(cur); // cur is copied to cur3
- During function return
-
Constructions vs. Assignment
- Every object is constructed once -> 赋值的时候不发生拷贝构造
- Every object should be destroyed once
- Failure to invoke delete()
- Invoking delete() more than once
- Once an object is constructed, it can be the target of many assignment operations
- 赋值、构造均为 member-wise, 即按照成员变量的顺序进行赋值、构造(bit-wise 是按照内存中的顺序进行全部赋值、构造)
Overloaded Operators | 重载运算符👀
- Allows user-defined types to act like built in types
- Another way to make a fucntion call
Note
unary and binary operators can be overloaded
+ - * / % ^ & | ~
= < > += -= *= /= %= ^= &= |=
<< >> >>= <<= == != <= >= && || ! ++ --
, ->* -> () []
new new[] delete delete[]
operators that cannot be overloaded
. .* :: ?:
sizeof typeid
static_cast dynamic_cast const_cast reinterpret_cast
- Restrictions
- Only existing operators can be overloaded (you can’t create a ** operator for exponentiation)
- Operators must be overloaded on a class or enumeration type
- Overloaded operators must
- Preserve number of operands
- Preserve precedence
C++ overloaded👀
- Just a function with an operator name ( Use the opertaor keyword as a prefix to name operator *(···) )
- Can be a member function
- Implicit first argument
const String String::operator +(const String &that);
string a, b; a + b;
-> be likea.operator+(b);
- Implicit first argument
- Can be a global function
- Both arguments explicit
const String operator +(const String &s1, const String &s2);
- Both arguments explicit
- 加
const
是为了防止a + b = c
这种情况发生
For Member functions👀
- Implicit first argument
- Developer must have access to the class definition
- Members have full access to the all data in the class
- No type conversion performed on receiver
class Integer
{
public:
Integer(int n = 0):i(n) {}
const Integer operator +(const Integer &that) const
{
return Integer(i + that.i);
}
private:
int i;
};
Integer x(1), y(5), z;
z = x + y; // x.operator+(y)
z = x + 3; // 此时会把 3 构造成一个 Integer 对象
z = 3 + y; // 此时不会把 3 变成 Integer 对象,但会尝试把 y 变成 int
- For binary operators(+, -, *, etc) member functions require one argument
- For unary operators(++, –, -, !, etc) member functions require no arguments
const Interger Interger::operator-() const{ return Integer(-i); }
z = -x
For global function👀
const Integer operator+ (const Integer& rhs, const Integer& lhs);
Integer x, y;
x + y; // -> operator+ (x, y)
- Explicit first argument
- Developer dose not need special access to classes
- May need to be a friend
- Type conversions performed on both arguments
conversions
-
Global operators
- binary operators requires two arguments
- unary operators require one
- If you don’t have access to private data members, then the global function must use the public interface or use
friend
Members vs. Free Function
- Unary operators should be members
= () [] -> ->*
must be members- assignment operators should be members
- All other binary operators as non-members
Argument Passing & Return Values👀
- Argument Passing
- If it is read-only, pass it in as a const reference (except for built-in types)
- make member functions const that do not modify the class (boolean operators, +, -, etc)
- for global functions, if the left-hand side changes, pass as a reference (assignment operators)
- Return Values
- Select the return type depending on the expected meaning of the operator. For example,
- For operator+, you need to generate a new object. Return as a const object so the result cannot be modified as an left-value.
- Logical operators should return bool(or int for older compilers)
- Select the return type depending on the expected meaning of the operator. For example,
Tip
- Pass in an object it you want to store it
- Pass in a reference or pointer if you want to do something to it
- Pass in a const reference or pointer if you want to get the values
- Pass out an object if you create it in the function
- Pass out a reference or pointer of the passed in only
- Never new something and return its pointer
The prototypes of operators👀
+ - * / % ^ & | ~
const Integer operator+ (const Integer& rhs, const Integer& lhs);
! && || < > <= >= == !=
bool operator< (const Integer& rhs, const Integer& lhs) const;
[]
- Must be a member function
- Single argument
- Implies that the object it is being called for acts like an array, so it should return a reference
Integer v[10]; v[0] = 1;
- if you return pointer -> you should use
*v[0] = 1;
-
++ --
- How to distinguish between prefix and postfix?
- Prefix:
const Integer& Integer::operator++();
- Postfix:
const Integer Integer::operator++(int);
- Prefix:
- postfix forms take an int argument – compiler will pass in 0 as that int
- User-defined prefix is more efficient than postfix
class Integer { public: const Integer& operator++() // prefix { *this += 1; // increment return *this; // fetch } // int argument not used so leave it unnamed // won't get compiler warning const Integer operator++(int) // postfix { Integer old = *this; // fetch ++(*this); // increment return old; // return old value } ··· Integer x(1); ++x; // calls x.operator++() x++; // calls x.operator++(0) };
- How to distinguish between prefix and postfix?
Stream👀
-
Defining a stream extractor
- Has to be a 2-argument global(free) function
- First argument is an
istream&
- Second argument is a reference to a value
- Return an
istream&
for chaining
-
Creating a stream inserter
- First argument is an
ostream&
- Second argument is any value
- Return an
ostream&
for chaining
- First argument is an
-
Creating manipulators
- You can define your own manipulators
-
Copying vs. Initialization
#include <iostream>
using namespace std;
class Fi
{
public:
Fi() { cout << "Fi()" << endl; }
};
class Fee
{
int i;
public:
Fee(int) { cout << "Fee(int)" << endl; }
Fee(const Fi&) { cout << "Fee(Fi)" << endl; }
Fee& operator=(const Fee& that)
{
i = that.i;
cout << "=()\n";
return *this;
}
};
int main()
{
Fee fee = 1; // Fee(int)
Fi fi;
Fee fum = fi; // Fee(Fi)
fum = fi;
}
- Assignment Operator
- For classes with dynamically allocated memory declare an assignment operator(and a copy constructor)
- To prevent assignment, explicitly declare
operator=
asprivate
Value classes👀
Note
- Appear to be primitive data types
- Passed to and returned from functions
- Have overloaded operators(often)
- Can be converted to and from others types
- like: Complex, Date, String
-
User-defined Type conversions
- A conversion operator can be used to convert an object of one class into an object of another class or a built-in type
-
Compilers perform implicit conversions using:
- Single-argument constructors
class PathName { string name; public: //or could be multi-argument with defaults PathName(const string&); ~PathName(); }; ··· string abc("abc"); PathName xyz(abc); //OK
-
implicit type conversion operators (Preventing implicit conversions)
-
更通用/直接的类型转换方法 — Operator conversion
- Function will be called automatically
- Return type is same as function name
class Rational { public: ··· operator double() const; // Rational to double (double() 可以是其他任何类型的名字) } ··· Rational::operator double() const { return numerator_/(double)denominator_; } Rational r(1, 3); double d = 1.3 * r; // r => double
General form of conversion ops
- X::operator T()
- Operator name is any type descriptor
- No explicit arguments
- No return type
- Complier will use it as a type conversion from
X
toT
-
C++ type conversions
- Built-in conversions
- Primitive
char -> short -> int -> float -> double (int -> long)
- Implicit (for any type T)
T -> T&; T& -> T; T* -> void*
T[] -> T*; T* -> T[]; T -> const T
- Primitive
-
User-defined
T -> C
- if
C(T)
is a valid constructor call forC
- if
operator C()
is defined forT
- if
-
But it’s better to avoid User-defined conversions. Use explicit conversion functions instead. For example:
- In
class Rational
instead of the conversion operator, declare a member functiondouble to_double() const;
- Built-in conversions
-
Overloading and type conversion
- C++ checks each argument for a “best match”
- Best match means cheapest
- Exact match is cost-free
- Matches involving built-in conversions
- User-defined type conversions
延申 - LValue vs. RValue
- 可以简单认为能出现在赋值号左边的都是左值:变量本身、引用;
*, []
运算的结果 - 只能出现在赋值号右边的都是右值:字面量;表达式
- 引用只能接受左值 -> 引用是左值的别名
- 调用函数时的传参相当于参数变量在调用时的初始化
右值引用
int x = 20;
左值int&& rx = x * 2;
x*2 的结果是一个右值,rx 延长其生命周期;rx 是右值引用(对右值的引用)-> 相当于把右值先固定下来int y = rx + 2;
因此你可以重用它:42rx = 100;
一旦你初始化一个右值引用变量,该变量就成为了一个左值,可以被赋值int&& rrx1 = x;
ERROR:右值引用无法被左值初始化const int&& rrx2 = x
ERROR: 右值引用无法被左值初始化
本文总阅读量:
次