4、complex部分分析(Class without pointer member)
5、常量成员函数(const member functions)
6、参数传递与返回值(pass / return by values VS pass /return by reference(to const))
1.简介
目标
-
以良好的方式编写C++class
class without pointer members--Complex
class with pointer members --String
-
学习Classes之间的关系
继承(inheritance)
复合(composition)
委托(delegation)
2、头文件与类的声明
3、防卫式声明
#ifndef __COMPLEX__
#define __COMPLEX__
/*
*/
#endif
头文件保护的目的是确保头文件中的内容只被编译一次,即使该头文件被多个源文件包含。如果没有这种保护,可能会导致编译错误,例如重复定义类、函数等。
这样,当 Complex.h
被包含进多个源文件时,只有第一次包含时 __COMPLEX__
宏未定义,Complex
类的定义会被编译。之后的包含由于 __COMPLEX__
已经定义,预处理器会跳过类的定义,从而避免了重复定义的问题。
4、complex部分分析(Class without pointer member)
#include<iostream>
using namespace std;
#ifndef __COMPLEX__
#define __COMPLEX__
class complex{
public:
complex(double r=0 ,double i = 0):re(r),im(i){}
/* complex(double r=0 ,double i = 0){re=r,im=i}*/
/*如果用户提供有参构造函数,编译器不提供默认构造函数(默认构造函数通常没有参数,但它们可以具有带默认值的参数。)
如果用户提供拷贝构造函数,编译器不提供其他构造函数*/
complex& operator += (const complex&);
double real() const { return re;}
/*类内定义的函数默认是内联的,但这并不意味着它们总是被内联。编译器会根据优化级别和其他因素来决定是否实际进行内联。对于模板类,即使成员函数不在类内定义,它们也是内联的,因为模板需要在每个实例化点生成代码。*/
double imag() const { return im;}
private:
double re,im;
friend complex& __doapl (complex*,const complex&);
};
inline complex&
__doapl(complex* ths,const complex& r){
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex&
complex::operator += (const complex& r){
return __doapl(this,r);
}
inline complex
operator + (const complex& x,const complex& y){
return complex(x.real()+y.real(),x.imag()+y.imag());
/*创建的临时对象,到下一行时,就会自动销毁,不能返回引用。创建的临时对象在栈。*/
}
inline complex
operator + (const complex& x,double y){
return complex(x.real()+y,x.imag());
}
inline complex
operator + (double x, const complex& y){
return complex(x + y.real(),y.imag());
}
ostream&
operator << (ostream& os,const complex& x){
return os << "(" <<x.real() << "," << x.imag() << ")";
}
#endif
1、内联函数
在C++中,inline
关键字用于建议编译器将一个函数的调用替换为该函数的代码。这样做可以减少函数调用的开销,特别是对于那些小的、频繁调用的函数。然而,是否真正内联该函数最终由编译器决定,编译器会根据多种因素(如代码大小、优化级别等)来决定是否采用内联。
内联函数的特点:
-
减少调用开销:内联函数避免了函数调用的额外开销,如参数传递、栈帧的创建和销毁等。
-
代码膨胀:由于内联函数的代码会在每个调用点展开,这可能导致程序体积增大。
-
编译器决定:编译器可以选择不内联一个函数,即使它被声明为
inline
。如果函数体较大或包含复杂的控制流语句(如循环和分支),编译器可能不会进行内联。 -
模板函数:模板函数默认是内联的,因为编译器需要在每个模板实例化点生成代码。
-
调试:内联函数可能会使调试稍微复杂一些,因为它们在源代码中的单一位置被展开到多个调用点。
-
优化:内联可以使得编译器有更多的优化机会,因为它可以看到函数调用的上下文。
-
成员函数:类内定义的函数默认是内联的,但这并不意味着它们总是被内联。
2、访问级别
3、初值列(initial list)
complex(double r=0 ,double i = 0):re(r),im(i){}
/* complex(double r=0 ,double i = 0){re=r,im=i}*/
在C++11及以后的版本中,构造函数的初值列(也称为成员初始化列表或构造函数初始化列表)是一个强大的特性,它允许你在构造函数中直接初始化成员变量,包括直接初始化成员对象和基类。使用初值列可以提高代码效率,并且对于某些类型的成员变量是必须的。
构造函数的初值列的优点:
-
效率:对于非构造函数的对象,使用初值列可以直接初始化,避免了复制或移动构造函数的调用。
-
必要性:对于常量成员、引用成员或没有默认构造函数的对象成员,必须使用初值列进行初始化。
-
清晰性:初值列提供了一种清晰的方式来展示成员变量的初始化顺序和方式。
-
性能:对于大型对象,使用初值列可以减少临时对象的创建,从而提高性能。
使用构造函数的初值列的注意事项:
-
顺序:在初值列中初始化成员变量的顺序应该与它们在类声明中的声明顺序相同。
-
必要性:对于引用类型或常量类型的成员变量,必须使用初值列进行初始化。
-
基类初始化:如果你的类继承自其他类,可以在初值列中初始化基类。例如,
BaseClass(name)
就是用name
对象来初始化基类BaseClass
。 -
列表结束:在初值列的最后,可以有一个冒号
:
后面跟着空格,然后是构造函数体的开始。 -
临时对象:使用初值列可以避免不必要的临时对象的创建,因为成员变量是直接使用提供的参数初始化的。
-
移动语义:如果成员变量是右值引用类型,初值列可以利用移动构造函数来提高效率。
-
异常安全:使用初值列可以提高异常安全性,因为成员变量的初始化是在构造函数体执行之前完成的,如果构造函数体中抛出异常,成员变量已经被正确初始化。
4、默认构造函数构造函数的重载
-
带参数的默认构造函数:
complex(double r=0, double i=0) : re(r), im(i) {}
这个构造函数有两个参数
r
和i
,它们都有默认值0。这意味着当创建complex
类的实例时,如果调用者没有提供这些参数的值,它们将默认为0。构造函数体内部使用了一个成员初始化列表来初始化成员变量re
和im
。这里的re
和im
代表复数的实部和虚部。 -
无参数的默认构造函数:
complex() : re(0), im(0) {}
这个构造函数不接受任何参数,并且在成员初始化列表中将
re
和im
初始化为0。这种构造函数通常用于创建一个默认的complex
实例,其实部和虚部都设置为0。由于只能有一个默认构造函数,因此不要同时采用这俩种方式。实际上,通常应初始化所有的对象,以确保所有成员一开始就有已知的合理值。因此,用户定义的默认构造函数通常会给所有成员提供隐式初始值。
3、使用构造函数
对于自定义的有参的构造函数,有两种使用构造函数来初始化对象的方式:
-
第一种显示的调用
complex comp = complex (10,5);
-
第二种隐式的调用
complex comp (10,5);
-
需要注意的是,当使用默认构造函数初始化对象,隐式调用不能带括号,否则变成函数的声明:
complex comp ();//错误
complex comp;//不能带括号
4、构造函数放在private中
5、常量成员函数(const member functions)
在C++中,常量成员函数是指在其声明的末尾加上 const
关键字的成员函数。这样的函数不能修改其所属对象的状态,即不能修改类的任何成员变量。它们可以被用于读取对象的状态,但不允许修改对象的状态。
1、常量成员函数的特点:
-
不可修改对象状态:常量成员函数不能改变对象的任何成员变量。
-
可以调用其他常量成员函数:常量成员函数可以调用类的其他常量成员函数。
-
可以被常量对象调用:常量对象只能调用常量成员函数。
-
提高封装性:使用常量成员函数可以提高类的封装性,因为它们不会改变对象的状态。
2、注意事项:
-
如果成员函数不修改对象的状态,最好将其声明为常量成员函数,这样可以提高代码的安全性和可读性。
-
常量成员函数可以访问和返回对象的成员变量,但不能修改它们。
-
常量成员函数不能调用非常量成员函数,否则会违反它们不能修改对象状态的原则。
-
常量对象只能调用常量成员函数,因为它们的状态不能被修改。
常量成员函数是C++中实现封装和保护对象状态的重要工具,它们确保了对象的不可变性,这在多线程编程和函数式编程中尤为重要。
6、参数传递与返回值(pass / return by values VS pass /return by reference(to const))
参考博客:细谈 C++ 返回传值的三种方式:按值返回、按常量引用返回以及按引用返回_常量引用返回值-CSDN博客
7、友元
在C++中,友元(Friend)是一种特殊的类成员或函数,它不属于类的私有或保护成员,但被授权可以访问类的私有(private)和保护(protected)成员。友元提供了一种方式,允许特定的函数或类访问另一个类的内部实现细节,即使这些细节本应是封装和隐藏的。
1、友元的主要特点:
-
访问权限:友元可以访问类的私有和保护成员,就像它们是类的成员函数一样。
-
非成员:友元函数不是类的成员,它们不属于类,但在类的定义中被声明。
-
类间关系:一个类可以声明另一个类的实例为其友元,或者声明另一个类的静态成员函数为其友元。
-
单向关系:友元关系是单向的,即如果类A是类B的友元,并不意味着类B自动成为类A的友元,除非显式声明。
-
声明方式:在类的定义中,使用
friend
关键字来声明友元。
2、注意事项:
-
封装性:过度使用友元可能会破坏类的封装性,因为它允许非成员函数访问类的私有成员。
-
控制范围:友元关系需要谨慎使用,因为它会扩大对类私有成员的访问范围。
-
单向关系:友元关系不具有传递性,即友元的友元不一定是友元。
-
类模板:在类模板中使用友元时需要特别注意,因为模板实例化可能会影响友元的可见性。
友元是C++中一个强大的特性,它提供了一种灵活的方式来访问类的私有成员,但应该谨慎使用,以避免破坏封装性和设计原则。
8、操作符重载
成员函数:
非成员函数
这些函数绝不可return by reference,因为,他们返回的必定是个local object。
ostream&
operator << (ostream& os,const complex& x){
return os << "(" <<x.real() << "," << x.imag() << ")";
}
-
函数签名:函数的返回类型是
ostream&
,这意味着函数应该返回一个输出流的引用,这样它就可以被链式调用。 -
参数类型:第二个参数是一个
const complex&
类型的引用,这表明该函数不会修改传入的复数对象,并且可以通过引用来避免复制。
5、complex源码
#ifndef __MYCOMPLEX__
#define __MYCOMPLEX__
class complex;
complex&
__doapl (complex* ths, const complex& r);
complex&
__doami (complex* ths, const complex& r);
complex&
__doaml (complex* ths, const complex& r);
class complex
{
public:
complex (double r = 0, double i = 0): re (r), im (i) { }
complex& operator += (const complex&);
complex& operator -= (const complex&);
complex& operator *= (const complex&);
complex& operator /= (const complex&);
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
friend complex& __doapl (complex *, const complex&);
friend complex& __doami (complex *, const complex&);
friend complex& __doaml (complex *, const complex&);
};
inline complex&
__doapl (complex* ths, const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex&
complex::operator += (const complex& r)
{
return __doapl (this, r);
}
inline complex&
__doami (complex* ths, const complex& r)
{
ths->re -= r.re;
ths->im -= r.im;
return *ths;
}
inline complex&
complex::operator -= (const complex& r)
{
return __doami (this, r);
}
inline complex&
__doaml (complex* ths, const complex& r)
{
double f = ths->re * r.re - ths->im * r.im;
ths->im = ths->re * r.im + ths->im * r.re;
ths->re = f;
return *ths;
}
inline complex&
complex::operator *= (const complex& r)
{
return __doaml (this, r);
}
inline double
imag (const complex& x)
{
return x.imag ();
}
inline double
real (const complex& x)
{
return x.real ();
}
inline complex
operator + (const complex& x, const complex& y)
{
return complex (real (x) + real (y), imag (x) + imag (y));
}
inline complex
operator + (const complex& x, double y)
{
return complex (real (x) + y, imag (x));
}
inline complex
operator + (double x, const complex& y)
{
return complex (x + real (y), imag (y));
}
inline complex
operator - (const complex& x, const complex& y)
{
return complex (real (x) - real (y), imag (x) - imag (y));
}
inline complex
operator - (const complex& x, double y)
{
return complex (real (x) - y, imag (x));
}
inline complex
operator - (double x, const complex& y)
{
return complex (x - real (y), - imag (y));
}
inline complex
operator * (const complex& x, const complex& y)
{
return complex (real (x) * real (y) - imag (x) * imag (y),
real (x) * imag (y) + imag (x) * real (y));
}
inline complex
operator * (const complex& x, double y)
{
return complex (real (x) * y, imag (x) * y);
}
inline complex
operator * (double x, const complex& y)
{
return complex (x * real (y), x * imag (y));
}
complex
operator / (const complex& x, double y)
{
return complex (real (x) / y, imag (x) / y);
}
inline complex
operator + (const complex& x)
{
return x;
}
inline complex
operator - (const complex& x)
{
return complex (-real (x), -imag (x));
}
inline bool
operator == (const complex& x, const complex& y)
{
return real (x) == real (y) && imag (x) == imag (y);
}
inline bool
operator == (const complex& x, double y)
{
return real (x) == y && imag (x) == 0;
}
inline bool
operator == (double x, const complex& y)
{
return x == real (y) && imag (y) == 0;
}
inline bool
operator != (const complex& x, const complex& y)
{
return real (x) != real (y) || imag (x) != imag (y);
}
inline bool
operator != (const complex& x, double y)
{
return real (x) != y || imag (x) != 0;
}
inline bool
operator != (double x, const complex& y)
{
return x != real (y) || imag (y) != 0;
}
#include <cmath>
inline complex
polar (double r, double t)
{
return complex (r * cos (t), r * sin (t));
}
inline complex
conj (const complex& x)
{
return complex (real (x), -imag (x));
}
inline double
norm (const complex& x)
{
return real (x) * real (x) + imag (x) * imag (x);
}
#endif //__MYCOMPLEX__