目录

 

C++ 类 & 对象

C++ 类定义

定义 C++ 对象

访问数据成员

类 & 对象详解

C++ 类成员函数

C++ 类访问修饰符

公有(public)成员

私有(private)成员

保护(protected)成员

继承中的特点

public 继承

protected 继承 

private 继承

C++ 类构造函数 & 析构函数

类的构造函数

带参数的构造函数

使用初始化列表来初始化字段

类的析构函数

C++ 拷贝构造函数

C++ 友元函数

C++ 内联函数

C++ this指针 

C++ 指向类的指针

C++ 类的静态成员

C ++ 静态成员函数

C++ 继承

基类 & 派生类

访问控制和继承

继承类型

多继承

C++ 重载运算符和重载函数

C++ 中的函数重载

C++ 中的运算符重载

可重载运算符/不可重载运算符

C++ 一元运算符重载

C++ 二元运算符重载 

C++ 关系运算符重载 

C++ 输入/输出运算符重载

C++ ++ 和 -- 运算符重载

C++ 赋值运算符重载

C++ 函数调用运算符 () 重载 

C++ 多态

虚函数

纯虚函数

C++ 接口(抽象类)

设计策略


C++ 类 & 对象

C++ 类定义

class Box
{public:double length;   // 盒子的长度double breadth;  // 盒子的宽度double height;   // 盒子的高度
};

关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的

定义 C++ 对象

Box Box1;          // 声明 Box1,类型为 Box
Box Box2;          // 声明 Box2,类型为 Box

对象 Box1 和 Box2 都有它们各自的数据成员。

访问数据成员

类的对象的公共数据成员可以使用直接成员访问运算符 (.) 来访问。

需要注意的是,私有的成员和受保护的成员不能使用直接成员访问运算符 (.) 来直接访问。我们将在后续的教程中学习如何访问私有成员和受保护的成员。

类 & 对象详解

到目前为止,我们已经对 C++ 的类和对象有了基本的了解。下面的列表中还列出了其他一些 C++ 类和对象相关的概念,可以点击相应的链接进行学习。

概念描述
类成员函数类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。
类访问修饰符类成员可以被定义为 public、private 或 protected。默认情况下是定义为 private。
构造函数 & 析构函数类的构造函数是一种特殊的函数,在创建一个新的对象时调用。类的析构函数也是一种特殊的函数,在删除所创建的对象时调用。
C++ 拷贝构造函数拷贝构造函数,是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
C++ 友元函数友元函数可以访问类的 private 和 protected 成员。
C++ 内联函数通过内联函数,编译器试图在调用函数的地方扩展函数体中的代码。
C++ 中的 this 指针每个对象都有一个特殊的指针 this,它指向对象本身。
C++ 中指向类的指针指向类的指针方式如同指向结构的指针。实际上,类可以看成是一个带有函数的结构。
C++ 类的静态成员类的数据成员和函数成员都可以被声明为静态的。

C++ 类成员函数

类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。

成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。

class Box
{public:double length;      // 长度double breadth;     // 宽度double height;      // 高度double getVolume(void){return length * breadth * height;}
};

也可以在类的外部使用范围解析运算符 :: 定义该函数。

double Box::getVolume(void)
{return length * breadth * height;
}

在这里,需要强调一点,在 :: 运算符之前必须使用类名。调用成员函数是在对象上使用点运算符(.),这样它就能操作与该对象相关的数据。

Box myBox;          // 创建一个对象myBox.getVolume();  // 调用该对象的成员函数

注意:在成员函数中,如果形参和属性同名会出错。

//正确
void setLength( double len )
{length = len;
}
//错误
void setLength( double length )
{length = length;
}

除了为形参起不同于属性的名字这个方法之外,还可以用this->length=length这样的写法,或者使用作用域符Box::length。

:: 叫作用域区分符,指明一个函数属于哪个类或一个数据属于哪个类。

:: 可以不跟类名,表示全局数据或全局函数(即非成员函数)。

C++中函数调用非虚成员函数、调用虚函数的区别

调用非虚成员函数:和调用非成员函数一样,通过对象确定对象所属的类,然后找到类的成员函数。此过程不会涉及到对象的内容,只会涉及对象的类型,是一种静态绑定

调用虚函数与调用非虚成员函数不同,需同过虚函数表找到虚函数的地址,而虚函数表存放在每个对象中,不能再编译期间实现。只能在运行时绑定,是一种动态绑定

C++ 类访问修饰符

数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。关键字 public、private、protected 称为访问修饰符。

一个类可以有多个 public、protected 或 private 标记区域。每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。成员和类的默认访问修饰符是 private

class Base {public:// 公有成员protected:// 受保护成员private:// 私有成员};

公有(public)成员

公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值。

#include <iostream>using namespace std;class Line
{public:double length;
};
int main( )
{Line line;line.length = 10.0; // OK: 因为 length 是公有的return 0;
}

私有(private)成员

私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员

默认情况下,类的所有成员都是私有的。例如在下面的类中,width 是一个私有成员,这意味着,如果您没有使用任何访问修饰符,类的成员将被假定为私有成员。

实际操作中,我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数。

#include <iostream>using namespace std;class Box
{public:double length;void setWidth( double wid );double getWidth( void );private:double width;    //width是私有数据
};// 成员函数定义
double Box::getWidth(void)
{return width ;    //类的成员函数可以访问类的私有数据
}void Box::setWidth( double wid )
{width = wid;    //类的成员函数可以访问类的私有数据
}// 程序的主函数
int main( )
{Box box;// 不使用成员函数设置长度box.length = 10.0; // OK: 因为 length 是公有的// 不使用成员函数设置宽度// box.width = 10.0; // Error: 因为 width 是私有的box.setWidth(10.0);  // OK:使用成员函数设置宽度return 0;
}

保护(protected)成员

保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的

在下一个章节中,您将学习到派生类和继承的知识。现在您可以看到下面的实例中,我们从父类 Box 派生了一个子.类 smallBox

#include <iostream>
using namespace std;class Box
{protected:double width;
};class SmallBox:Box // SmallBox 是派生类
{public:void setSmallWidth( double wid );double getSmallWidth( void );
};
class NotBo
{public:double getWidth();
}
// 子类的成员函数
double SmallBox::getSmallWidth(void)
{return width ;
}void SmallBox::setSmallWidth( double wid )
{width = wid;
}//非子类的成员函数
double NotBox::getWidth(void)
{return Box::width;
}// 程序的主函数
int main( )
{SmallBox box;NotBox notbox;// 使用成员函数设置宽度box.setSmallWidth(5.0);// 调用非子类的成员函数notbox.getWidth();    //Error,无法访问Box的protected数据return 0;
}

继承中的特点

有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。

  • 1.public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private【将基类的修饰符原封不动地继承下来】

  • 2.protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private【将基类中的public变为protected,其他修饰符原封不动继承下来】

  • 3.private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private【将基类中public、protected变为private,其他修饰符(也只有private了)原封不动既传承下来】

简单来说,若将三种继承方式按可访问的范围大小来排序,则有public > protected > private

那么子类继承基类时,可以理解为发生了“短板效应”,可访问范围大的修饰符会变为可访问范围小的修饰符。

比如,以protected方式继承基类,那么public会向protected变化,protected不变,private由于可访问范围比protected更小,所以private也不变。

但无论哪种继承方式,上面两点都没有改变:

  • 1.private 成员只能被本类成员(类内)和友元访问,不能被派生类访问

  • 2.protected 成员可以被派生类(子类)访问。

public 继承

#include<iostream>
#include<assert.h>
using namespace std;class A{
public:int a;A(){a1 = 1;a2 = 2;a3 = 3;a = 4;}void fun(){cout << a << endl;    //正确cout << a1 << endl;   //正确cout << a2 << endl;   //正确cout << a3 << endl;   //正确}
public:int a1;
protected:int a2;
private:int a3;
};
class B : public A{
public:int a;B(int i){A();a = i;}void fun(){cout << a << endl;       //正确,public成员cout << a1 << endl;       //正确,基类的public成员,在派生类中仍是public成员。cout << a2 << endl;       //正确,基类的protected成员,在派生类中仍是protected可以被派生类访问。cout << a3 << endl;       //错误,基类的private成员不能被派生类访问。}
};
int main(){B b(10);cout << b.a << endl;cout << b.a1 << endl;   //正确cout << b.a2 << endl;   //错误,派生类对象不能访问protected成员cout << b.a3 << endl;   //错误,类外不能访问private成员system("pause");return 0;
}

注意:protected,表示有条件的限制以保护该成员,当将类别成员定义为protected之后,继承它的类就可以直接使用这些成员,但这些成员仍然受到对象范围的保护,不可被派生类(子类)对象直接使用。

protected 继承 

#include<iostream>
#include<assert.h>
using namespace std;
class A{
public:int a;A(){a1 = 1;a2 = 2;a3 = 3;a = 4;}void fun(){cout << a << endl;    //正确cout << a1 << endl;   //正确cout << a2 << endl;   //正确cout << a3 << endl;   //正确}
public:int a1;
protected:int a2;
private:int a3;
};
class B : protected A{
public:int a;B(int i){A();a = i;}void fun(){cout << a << endl;       //正确,public成员。cout << a1 << endl;       //正确,基类的public成员,在派生类中变成了protected,可以被派生类访问。cout << a2 << endl;       //正确,基类的protected成员,在派生类中还是protected,可以被派生类访问。cout << a3 << endl;       //错误,基类的private成员不能被派生类访问。}
};
int main(){B b(10);cout << b.a << endl;       //正确。public成员cout << b.a1 << endl;      //错误,protected成员不能被派生类对象访问。cout << b.a2 << endl;      //错误,protected成员不能被派生类对象访问。cout << b.a3 << endl;      //错误,private成员不能在类外访问。system("pause");return 0;
}

private 继承

#include<iostream>
#include<assert.h>
using namespace std;
class A{
public:int a;A(){a1 = 1;a2 = 2;a3 = 3;a = 4;}void fun(){cout << a << endl;    //正确cout << a1 << endl;   //正确cout << a2 << endl;   //正确cout << a3 << endl;   //正确}
public:int a1;
protected:int a2;
private:int a3;
};
class B : private A{
public:int a;B(int i){A();a = i;}void fun(){cout << a << endl;       //正确,public成员。cout << a1 << endl;       //正确,基类public成员,在派生类中变成了private,可以被派生类访问。cout << a2 << endl;       //正确,基类的protected成员,在派生类中变成了private,可以被派生类访问。cout << a3 << endl;       //错误,基类的private成员不能被派生类访问。}
};
int main(){B b(10);cout << b.a << endl;       //正确。public成员cout << b.a1 << endl;      //错误,private成员不能在类外访问。cout << b.a2 << endl;      //错误, private成员不能在类外访问。cout << b.a3 << endl;      //错误,private成员不能在类外访问。system("pause");return 0;
}

注意:因为私有继承才在派生类中变为private属性的数据在派生类内依然可以被访问,但在基类中本来就是private属性的数据在派生类内不可被访问。(基类的私有属性有被派生类继承下来,但在派生类内不可访问)

 如果继承时不显示声明是 private,protected,public 继承,则默认是 private 继承,在 struct 中默认 public 继承。

继承方式基类的public成员基类的protected成员基类的private成员继承引起的访问控制关系变化概括
public继承仍为public成员仍为protected成员不可见基类的非私有成员在子类的访问属性不变
protected继承变为protected成员变为protected成员不可见基类的非私有成员都为子类的保护成员
private继承变为private成员变为private成员不可见基类的非私有成员都成为子类的私有成员

C++ 类构造函数 & 析构函数

类的构造函数

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行

构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。

#include <iostream>using namespace std;class Line
{public:void setLength( double len );double getLength( void );Line();  // 这是构造函数private:double length;
};// 成员函数定义,包括构造函数
Line::Line(void)
{cout << "Object is being created" << endl;
}void Line::setLength( double len )
{length = len;
}double Line::getLength( void )
{return length;
}
// 程序的主函数
int main( )
{Line line;    //构造函数在这一步执行// 设置长度line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl;return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://Object is being created
//Length of line : 6

从上面的例子可以看出,构造函数在创建对象时执行。

带参数的构造函数

默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值。

#include <iostream>using namespace std;class Line
{public:void setLength( double len );double getLength( void );Line(double len);  // 这是构造函数private:double length;
};// 成员函数定义,包括构造函数
Line::Line( double len)
{cout << "Object is being created, length = " << len << endl;length = len;
}void Line::setLength( double len )
{length = len;
}double Line::getLength( void )
{return length;
}
// 程序的主函数
int main( )
{Line line(10.0);// 获取默认设置的长度cout << "Length of line : " << line.getLength() <<endl;// 再次设置长度line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl;return 0;
}

P.S:构造函数可以在类内定义,也可以类内声明类外定义(必须指明作用域),上面的例子就是在类内声明,类外定义。而下面的例子是在类内声明并定义。

#include <iostream>
using namespace std;
class rectangle
{int length;int width;
public:int square(){return length*width;}rectangle(int length,int width){this->length=length;this->width=width;}
};
int main()
{rectangle rec(10,20);cout<<rec.square()<<endl;return 0;
}

使用初始化列表来初始化字段

使用初始化列表来初始化字段:

Line::Line( double len): length(len)
{cout << "Object is being created, length = " << len << endl;
}

上面的语法等同于如下语法:

Line::Line( double len)
{length = len;cout << "Object is being created, length = " << len << endl;
}

假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:

C::C( double a, double b, double c): X(a), Y(b), Z(c)
{....
}

P.S:使用初始化列表允许形参与类的属性同名

#include <iostream>
using namespace std;
class rectangle
{int length;int width;
public:int square(){return length*width;}rectangle(int length,int width):length(length),width(width){}
};
int main()
{rectangle rec(10,20);cout<<rec.square()<<endl;return 0;
}

P.S:C++ 初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。

class CMyClass {CMyClass(int x, int y);int m_x;    //声明的顺序是先m_x,再m_yint m_y;
};CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y)
{
};

编译器先初始化 m_x,然后是 m_y,,因为它们是按这样的顺序声明的。 

但只要成员变量的初始化不依赖其他成员变量,即使顺序不同也能正确的初始化。

类的析构函数

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行

析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

#include <iostream>using namespace std;class Line
{public:Line();   // 这是构造函数声明~Line();  // 这是析构函数声明private:double length;
};// 成员函数定义,包括构造函数
Line::Line(void)
{cout << "Object is being created" << endl;
}
Line::~Line(void)
{cout << "Object is being deleted" << endl;
}
// 程序的主函数
int main( )
{Line line;return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://Object is being created
//Object is being deleted

从上面的例子可以看出,虽然我们没有手动调用类的析构函数,但是在程序的最后,系统会自动调用析构函数。

C++ 拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象

  • 复制对象把它作为参数传递给函数

  • 复制对象,并从函数返回这个对象

拷贝构造函数具有单个形参,该形参(常用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用拷贝构造函数。当该类型的对象传递给函数或从函数返回该类型的对象时,将隐式调用拷贝构造函数。 

拷贝构造函数的最常见形式如下:

classname (const classname &obj) {// 构造函数的主体
}

在这里,obj 是一个对象引用,该对象是用于初始化另一个对象的。

#include <iostream>using namespace std;class Line
{public:int getLength( void );Line( int len );             // 简单的构造函数Line( const Line &obj);      // 拷贝构造函数~Line();                     // 析构函数private:int *ptr;
};// 成员函数定义,包括构造函数
Line::Line(int len)
{cout << "调用构造函数" << endl;// 为指针分配内存ptr = new int;*ptr = len;    //类带有指针变量,且有动态内存分配,则必须有拷贝构造函数
}Line::Line(const Line &obj)
{cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;ptr = new int;*ptr = *obj.ptr; // 拷贝值
}Line::~Line(void)
{cout << "释放内存" << endl;delete ptr;
}
int Line::getLength( void )
{return *ptr;
}void display(Line obj)
{cout << "line 大小 : " << obj.getLength() <<endl;
}// 程序的主函数
int main( )
{Line line(10);display(line);    //类的对象作为函数参数,需要调用拷贝构造函数return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://调用构造函数
//调用拷贝构造函数并为指针 ptr 分配内存
//line 大小 : 10
//释放内存
//释放内存

下面的实例对上面的实例稍作修改,通过使用已有的同类型的对象来初始化新创建的对象

#include <iostream>using namespace std;class Line
{public:int getLength( void );Line( int len );             // 简单的构造函数Line( const Line &obj);      // 拷贝构造函数~Line();                     // 析构函数private:int *ptr;
};// 成员函数定义,包括构造函数
Line::Line(int len)
{cout << "调用构造函数" << endl;// 为指针分配内存ptr = new int;*ptr = len;
}Line::Line(const Line &obj)
{cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;ptr = new int;*ptr = *obj.ptr; // 拷贝值
}Line::~Line(void)
{cout << "释放内存" << endl;delete ptr;
}
int Line::getLength( void )
{return *ptr;
}void display(Line obj)
{cout << "line 大小 : " << obj.getLength() <<endl;
}// 程序的主函数
int main( )
{Line line1(10);Line line2 = line1; // 这里也调用了拷贝构造函数display(line1);display(line2);return 0;
}//当上面的代码被编译和执行时,它会产生下列结果:
//调用构造函数
//调用拷贝构造函数并为指针 ptr 分配内存    ————line2=line1
//调用拷贝构造函数并为指针 ptr 分配内存    ————display(line1)
//line 大小 : 10
//释放内存
//调用拷贝构造函数并为指针 ptr 分配内存    ————display(line2)
//line 大小 : 10
//释放内存
//释放内存
//释放内存

P.S:对象作为函数参数时调用了拷贝构造函数,在函数执行结束时自动调用析构函数,回收内存。

C++支持两种初始化形式

拷贝初始化 int a = 5; 和直接初始化 int a(5); 对于其他类型没有什么区别,对于类类型直接初始化直接调用实参匹配的构造函数,拷贝初始化总是调用拷贝构造函数,也就是说:

A x(2);  //直接初始化,调用构造函数
A y = x;  //拷贝初始化,调用拷贝构造函数

如果在类中没有定义拷贝构造函数,编译器会自行定义一个但对于凡是包含动态分配成员或包含指针成员的类,都应该提供拷贝构造函数。

必须定义拷贝构造函数的情况

只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义拷贝构造函数也可以拷贝

有的类有一个数据成员是指针,或者是有成员表示在构造函数中分配的其他资源,这两种情况下都必须定义拷贝构造函数。

关于为什么当类成员中含有指针类型成员且需要对其分配内存时,一定要有总定义拷贝构造函数?

默认的拷贝构造函数实现的只能是浅拷贝,即直接将原对象的数据成员值依次复制给新对象中对应的数据成员,并没有为新对象另外分配内存资源

这样,如果对象的数据成员是指针,两个指针对象实际上指向的是同一块内存空间

在某些情况下,浅拷贝回带来数据安全方面的隐患。

当类的数据成员中有指针类型时,我们就必须定义一个特定的拷贝构造函数,该拷贝构造函数不仅可以实现原对象和新对象之间数据成员的拷贝,而且可以为新的对象分配单独的内存资源,这就是深拷贝构造函数。

如何防止默认拷贝发生?

声明一个私有的拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类的对象,编译器会报告错误,从而可以避免按值传递或返回对象。

总结:

当出现类的等号赋值时,会调用拷贝函数,在未定义显式拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。

深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。

C++ 友元函数

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend。

class Box
{double width;
public:double length;friend void printWidth( Box box );    //可以访问Box的私有数据和方法void setWidth( double wid );
};

声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置如下声明:

friend class ClassTwo;
#include <iostream>using namespace std;class Box
{double width;
public:friend void printWidth( Box box );void setWidth( double wid );
};// 成员函数定义
void Box::setWidth( double wid )
{width = wid;
}// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */cout << "Width of box : " << box.width <<endl;
}// 程序的主函数
int main( )
{Box box;// 使用成员函数设置宽度box.setWidth(10.0);// 使用友元函数输出宽度printWidth( box );return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://Width of box : 10

友元函数没有this指针,则参数要有三种情况: 

要访问非static成员时,需要对象做参数;

要访问static成员或全局变量时,则不需要对象做参数;

如果做参数的对象是全局对象,则不需要对象做参数;

可以直接调用友元函数,不需要通过对象或指针。

下面是友元类的应用:

#include <iostream>using namespace std;class Box
{double width;
public:friend void printWidth(Box box);    //声明友元函数friend class BigBox;    //声明友元类void setWidth(double wid);
};//定义友元类
class BigBox    
{
public :void Print(int width, Box &box){// BigBox是Box的友元类,它可以直接访问Box类的任何成员box.setWidth(width);cout << "Width of box : " << box.width << endl;}
};// 成员函数定义
void Box::setWidth(double wid)
{width = wid;
}// 友元函数定义,它不属于任何类
void printWidth(Box box)
{//因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员cout << "Width of box : " << box.width << endl;
}// 程序的主函数
int main()
{Box box;BigBox big;// 使用成员函数设置宽度box.setWidth(10.0);// 使用友元函数输出宽度printWidth(box);// 使用友元类中的方法设置宽度big.Print(20, box);return 0;
}

C++ 内联函数

C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。

在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。

#include <iostream>using namespace std;inline int Max(int x, int y)
{return (x > y)? x : y;
}// 程序的主函数
int main( )
{cout << "Max (20,10): " << Max(20,10) << endl;cout << "Max (0,200): " << Max(0,200) << endl;cout << "Max (100,1010): " << Max(100,1010) << endl;return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://Max (20,10): 20
//Max (0,200): 200
//Max (100,1010): 1010

引入内联函数的目的是为了解决程序中函数调用的效率问题。这么说吧,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的节省。所以内联函数一般都是1-5行的小函数。在使用内联函数时要留神:

  • 1.在内联函数内不允许使用循环语句和开关语句
  • 2.内联函数的定义必须出现在内联函数第一次调用之前
  • 3.类结构中所在的类说明内部定义的函数是内联函数。

C++ this指针 

在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。

友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。

因为 this 的目的总是指向“这个”对象,所以 this 是一个常量指针,我们不允许改变 this 中保存的地址。

在成员函数内部,我们可以直接使用或调用该函数的对象的成员,而无须通过成员访问运算符来做到这一点,因为 this 所指的正是这个对象。任何对类成员的直接访问都被看作是对 this 的隐式引用。即在内部访问成员xx,等价于访问this->xx。

this 指针的类型与它所属的类一致。例如,对于类Book,它的对象指针类型就是Book*。

C++ 指向类的指针

一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,必须在使用指针之前,对指针进行初始化。

#include <iostream>using namespace std;class Box
{public:// 构造函数定义Box(double l=2.0, double b=2.0, double h=2.0){cout <<"Constructor called." << endl;length = l;breadth = b;height = h;}double Volume(){return length * breadth * height;}private:double length;     double breadth;    double height;     
};int main(void)
{Box Box1(3.3, 1.2, 1.5);   Box Box2(8.5, 6.0, 2.0);    Box *ptrBox;                // 声明一个指向类的指针// 保存第一个对象的地址ptrBox = &Box1;// 现在尝试使用成员访问运算符来访问成员cout << "Volume of Box1: " << ptrBox->Volume() << endl;// 保存第二个对象的地址ptrBox = &Box2;// 现在尝试使用成员访问运算符来访问成员cout << "Volume of Box2: " << ptrBox->Volume() << endl;return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://Constructor called.
//Constructor called.
//Volume of Box1: 5.94
//Volume of Box2: 102

C++ 类的静态成员

我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示。

#include <iostream>using namespace std;class Box
{public:static int objectCount;    //静态成员的声明,在类内// 构造函数定义Box(double l=2.0, double b=2.0, double h=2.0){cout <<"Constructor called." << endl;length = l;breadth = b;height = h;// 每次创建对象时增加 1objectCount++;}double Volume(){return length * breadth * height;}private:double length;     // 长度double breadth;    // 宽度double height;     // 高度
};// 初始化类 Box 的静态成员,必须在类外
int Box::objectCount = 0;int main(void)
{Box Box1(3.3, 1.2, 1.5);    // 声明 box1Box Box2(8.5, 6.0, 2.0);    // 声明 box2// 输出对象的总数cout << "Total objects: " << Box::objectCount << endl;return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://Constructor called.
//Constructor called.
//Total objects: 2

P.S:静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义,实际上是给静态成员变量分配内存。如果不加定义就会报错,初始化是赋一个初始值,而定义是分配内存。

C ++ 静态成员函数

如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。

静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。

静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建,也可以使用静态成员变量清楚了解构造与析构函数的调用情况。

静态成员函数与普通成员函数的区别:

  • 静态成员函数没有 this 指针(因为它不专属于哪一个对象),只能访问静态成员(包括静态成员变量和静态成员函数)。
  • 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
#include <iostream>using namespace std;class Box
{public:static int objectCount;    //静态成员的声明// 构造函数定义Box(double l=2.0, double b=2.0, double h=2.0){cout <<"Constructor called." << endl;length = l;breadth = b;height = h;// 每次创建对象时增加 1objectCount++;}double Volume(){return length * breadth * height;}static int getCount()    //静态成员函数的定义{return objectCount;}private:double length;     // 长度double breadth;    // 宽度double height;     // 高度
};// 初始化类 Box 的静态成员
int Box::objectCount = 0;int main(void)
{// 在创建对象之前输出对象的总数cout << "Inital Stage Count: " << Box::getCount() << endl;Box Box1(3.3, 1.2, 1.5);    // 声明 box1Box Box2(8.5, 6.0, 2.0);    // 声明 box2// 在创建对象之后输出对象的总数cout << "Final Stage Count: " << Box::getCount() << endl;return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://Inital Stage Count: 0
//Constructor called.
//Constructor called.
//Final Stage Count: 2

P.S:与静态成员变量一样,调用类的静态成员函数也要用范围解析运算符 :: 。

类中特殊成员变量的初始化问题:

  • 常量变量:必须通过构造函数参数列表进行初始化。
  • 引用变量:必须通过构造函数参数列表进行初始化。
  • 普通静态变量:要在类外通过"::"初始化。
  • 静态整型常量:可以直接在定义的时候初始化。
  • 静态非整型常量:不能直接在定义的时候初始化。要在类外通过"::"初始化。

C++ 继承

面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。

当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类

基类 & 派生类

一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名。

class derived-class: access-specifier base-class
class dog:public animal

P.S:如果未使用访问修饰符 access-specifier,则默认为 private 继承。

 假设有一个基类 ShapeRectangle 是它的派生类

#include <iostream>using namespace std;// 基类
class Shape 
{public:void setWidth(int w){width = w;}void setHeight(int h){height = h;}protected:int width;int height;
};// 派生类
class Rectangle: public Shape
{public:int getArea(){ return (width * height);     //width和height被Rectangle类继承,作为它的保护成员}
};int main(void)
{Rectangle Rect;Rect.setWidth(5);Rect.setHeight(7);// 输出对象的面积cout << "Total area: " << Rect.getArea() << endl;return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://Total area: 35

访问控制和继承

派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。

我们可以根据访问权限总结出不同的访问类型,如下所示:

访问publicprotectedprivate
同一个类yesyesyes
派生类yesyesno
外部的类yesnono

一个派生类继承了所有的基类方法,但下列情况除外:

  • 基类的构造函数、析构函数和拷贝构造函数。
  • 基类的重载运算符。
  • 基类的友元函数。

继承类型

当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。

我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

  • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问(基类私有成员对于派生类不可见),但是可以通过调用基类的公有保护成员来访问
  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有保护成员将成为派生类的保护成员。
  • 私有继承(private):当一个类派生自私有基类时,基类的公有保护成员将成为派生类的私有成员。

P.S:私有继承将基类的成员变为派生类自己的私有成员 跟 用某种方式继承了基类中的私有成员 是不一样的。 

多继承

多继承即一个子类可以有多个父类,它继承了多个父类的特性。

C++ 类可以从多个类继承成员。

class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
class Me:public Women,public Student
{...
};

其中,访问修饰符继承方式是 public、protected 或 private 其中的一个,用来修饰每个基类,各个基类之间用逗号分隔。

另外一种多继承(环状继承,也叫菱形继承),A->Base, C->Base, D->(A,C),例如:

class Base{......};
class A: public Base{......};
class C: public Base{......};
class D: public A, public C{.....};

这个继承会使D创建两个对象,要解决上面问题就要用虚拟继承格式

格式:class 类名: virtual 继承方式 父类名class A:virtual public Base
{...
};
class C:virtual public Base
{...
};
#include <iostream>using namespace std;
//基类class Base
{
public:Base(){cout<<"Base()"<<endl;}~Base(){cout<<"~Base()"<<endl;}
protected:int base;
};class A:virtual public Base
{
public:A(){cout<<"A()"<<endl;}~A(){cout<<"~A()"<<endl;}
protected:int a;
};class C:virtual public Base
{
public:C(){cout<<"C()"<<endl;}~C(){cout<<"~C()"<<endl;}
protected:int c;
};class D:public C, public A
{
public:D(){cout<<"D()"<<endl;}~D(){cout<<"~D()"<<endl;}
protected:int d;
};int main()
{cout << "Hello World!" << endl;D d;   //Base, A, C ,DBase base;A a;C c;cout<<sizeof(d)<<endl;    //40cout<<sizeof(base)<<endl;    //4cout<<sizeof(a)<<endl;    //16cout<<sizeof(c)<<endl;    //16return 0;
}

C++ 重载运算符和重载函数

C++ 允许在同一作用域中的某个函数运算符指定多个定义,分别称为函数重载运算符重载

重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明但是它们的参数列表和定义(实现)不相同。

当您调用一个重载函数重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策

C++ 中的函数重载

在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。

#include <iostream>
using namespace std;class printData
{public:void print(int i) {cout << "整数为: " << i << endl;}void print(double  f) {cout << "浮点数为: " << f << endl;}void print(char c[]) {cout << "字符串为: " << c << endl;}
};int main(void)
{printData pd;// 输出整数pd.print(5);// 输出浮点数pd.print(500.263);// 输出字符串char c[] = "Hello C++";pd.print(c);return 0;
}

C++ 中的运算符重载

您可以重定义或重载大部分 C++ 内置的运算符。这样,您就能使用自定义类型的运算符。

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。

Box operator+(const Box&);

声明加法运算符用于把两个 Box 对象相加,返回最终的 Box 对象。大多数的重载运算符可被定义为普通的非成员函数或者被定义为类成员函数。如果我们定义上面的函数为类的非成员函数,那么我们需要为每次操作传递两个参数。

Box operator+(const Box&, const Box&);

下面的实例使用成员函数演示了运算符重载的概念。在这里,对象作为参数进行传递,对象的属性使用 this 运算符进行访问。

#include <iostream>
using namespace std;class Box
{public:double getVolume(void){return length * breadth * height;}void setLength( double len ){length = len;}void setBreadth( double bre ){breadth = bre;}void setHeight( double hei ){height = hei;}// 重载 + 运算符,用于把两个 Box 对象相加Box operator+(const Box& b){Box box;box.length = this->length + b.length;box.breadth = this->breadth + b.breadth;box.height = this->height + b.height;return box;}private:double length;      // 长度double breadth;     // 宽度double height;      // 高度
};
// 程序的主函数
int main( )
{Box Box1;                // 声明 Box1,类型为 BoxBox Box2;                // 声明 Box2,类型为 BoxBox Box3;                // 声明 Box3,类型为 Boxdouble volume = 0.0;     // 把体积存储在该变量中// Box1 详述Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0);// Box2 详述Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0);// Box1 的体积volume = Box1.getVolume();cout << "Volume of Box1 : " << volume <<endl;// Box2 的体积volume = Box2.getVolume();cout << "Volume of Box2 : " << volume <<endl;// 把两个对象相加,得到 Box3Box3 = Box1 + Box2;    //这里自动调用了拷贝构造函数// Box3 的体积volume = Box3.getVolume();cout << "Volume of Box3 : " << volume <<endl;return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://Volume of Box1 : 210
//Volume of Box2 : 1560
//Volume of Box3 : 5400

可重载运算符/不可重载运算符

下面是可重载的运算符列表:

双目算术运算符+ (加),-(减),*(乘),/(除),% (取模)
关系运算符==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于)
逻辑运算符||(逻辑或),&&(逻辑与),!(逻辑非)
单目运算符+ (正),-(负),*(指针),&(取地址)
自增自减运算符++(自增),--(自减)
位运算符| (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移)
赋值运算符=, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>=
空间申请与释放new, delete, new[ ] , delete[]
其他运算符()(函数调用),->(成员访问),,(逗号),[](下标)

下面是不可重载的运算符列表:

  • .:成员访问运算符
  • .*, ->*:成员指针访问运算符
  • :::域运算符
  • sizeof:长度运算符
  • ?::条件运算符
  • #: 预处理符号

注意:

  • 运算重载符不可以改变语法结构。
  • 运算重载符不可以改变操作数的个数。
  • 运算重载符不可以改变优先级。
  • 运算重载符不可以改变结合性。

类重载、覆盖、重定义之间的区别:

  • 重载指的是函数具有的不同的参数列表,而函数名相同的函数。重载要求参数列表必须不同,比如参数的类型不同、参数的个数不同、参数的顺序不同。如果仅仅是函数的返回值不同是没办法重载的,因为重载要求参数列表必须不同。(发生在同一个类里)
  •  覆盖是存在类中,子类重写从基类继承过来的函数被重写的函数不能是static的。必须是virtual的。但是函数名、返回值、参数列表都必须和基类相同(发生在基类和子类)
  •  重定义也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。(发生在基类和子类)

C++ 一元运算符重载

一元运算符只对一个操作数进行操作,下面是一元运算符的实例:

  • 递增运算符( ++ )和递减运算符( -- )
  • 一元减运算符,即负号( - )
  • 逻辑非运算符( ! )

一元运算符通常出现在它们所操作的对象的左边,比如 !obj、-obj 和 ++obj,但有时它们也可以作为后缀,比如 obj++ 或 obj--。

下面的实例演示了如何重载一元减运算符( - )。

#include <iostream>
using namespace std;class Distance
{private:int feet;             // 0 到无穷int inches;           // 0 到 12public:// 所需的构造函数Distance(){feet = 0;inches = 0;}Distance(int f, int i){feet = f;inches = i;}// 显示距离的方法void displayDistance(){cout << "F: " << feet << " I:" << inches <<endl;}// 重载负运算符( - )Distance operator- ()  {feet = -feet;inches = -inches;return Distance(feet, inches);}
};
int main()
{Distance D1(11, 10), D2(-5, 11);-D1;                     // 取相反数D1.displayDistance();    // 距离 D1-D2;                     // 取相反数D2.displayDistance();    // 距离 D2return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://F: -11 I:-10
//F: 5 I:-11

C++ 二元运算符重载 

二元运算符需要两个参数,下面是二元运算符的实例。我们平常使用的加运算符( + )、减运算符( - )、乘运算符( * )和除运算符( / )都属于二元运算符。就像加(+)运算符。

下面的实例演示了如何重载加运算符( + )。类似地,您也可以尝试重载减运算符( - )和除运算符( / )。

#include <iostream>
using namespace std;class Box
{double length;      // 长度double breadth;     // 宽度double height;      // 高度
public:double getVolume(void){return length * breadth * height;}void setLength( double len ){length = len;}void setBreadth( double bre ){breadth = bre;}void setHeight( double hei ){height = hei;}// 重载 + 运算符,用于把两个 Box 对象相加Box operator+(const Box& b){Box box;box.length = this->length + b.length;box.breadth = this->breadth + b.breadth;box.height = this->height + b.height;return box;}
};
// 程序的主函数
int main( )
{Box Box1;                // 声明 Box1,类型为 BoxBox Box2;                // 声明 Box2,类型为 BoxBox Box3;                // 声明 Box3,类型为 Boxdouble volume = 0.0;     // 把体积存储在该变量中// Box1 详述Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0);// Box2 详述Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0);// Box1 的体积volume = Box1.getVolume();cout << "Volume of Box1 : " << volume <<endl;// Box2 的体积volume = Box2.getVolume();cout << "Volume of Box2 : " << volume <<endl;// 把两个对象相加,得到 Box3Box3 = Box1 + Box2;// Box3 的体积volume = Box3.getVolume();cout << "Volume of Box3 : " << volume <<endl;return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://Volume of Box1 : 210
//Volume of Box2 : 1560
//Volume of Box3 : 5400

P.S:符号重载函数不仅可以用类的成员函数来实现,也可以用友元函数来实现。

#include <iostream>
using namespace std;class Box
{double length;      // 长度double breadth;     // 宽度double height;      // 高度
public:double getVolume(void){return length * breadth * height;}void setLength( double len ){length = len;}void setBreadth( double bre ){breadth = bre;}void setHeight( double hei ){height = hei;}/*** 重载 + 运算符,用于把两个 Box 对象相加* 因为其是全局函数,对应的参数个数为2。* 当重载的运算符函数是全局函数时,需要在类中将该函数声明为友元。*/friend Box operator+(const Box& a, const Box& b);    //友元函数声明};Box operator+(const Box& a, const Box& b)    //在类外定义友元函数,它可以访问类的私有属性
{Box box;box.length = a.length + b.length;box.breadth = a.breadth + b.breadth;box.height = a.height + b.height;return box;
}// 程序的主函数
int main( )
{Box Box1;                // 声明 Box1,类型为 BoxBox Box2;                // 声明 Box2,类型为 BoxBox Box3;                // 声明 Box3,类型为 Boxdouble volume = 0.0;     // 把体积存储在该变量中// Box1 详述Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0);// Box2 详述Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0);// Box1 的体积volume = Box1.getVolume();cout << "Volume of Box1 : " << volume <<endl;// Box2 的体积volume = Box2.getVolume();cout << "Volume of Box2 : " << volume <<endl;// 把两个对象相加,得到 Box3Box3 = Box1 + Box2;// Box3 的体积volume = Box3.getVolume();cout << "Volume of Box3 : " << volume <<endl;return 0;
}

C++ 关系运算符重载 

C++ 语言支持各种关系运算符( < 、 > 、 <= 、 >= 、 == 等等),它们可用于比较 C++ 内置的数据类型。

您可以重载任何一个关系运算符,重载后的关系运算符可用于比较类的对象。

下面的实例演示了如何重载 < 运算符,类似地,您也可以尝试重载其他的关系运算符。

#include <iostream>
using namespace std;class Distance
{private:int feet;             // 0 到无穷int inches;           // 0 到 12public:// 所需的构造函数Distance(){feet = 0;inches = 0;}Distance(int f, int i){feet = f;inches = i;}// 显示距离的方法void displayDistance(){cout << "F: " << feet << " I:" << inches <<endl;}// 重载负运算符( - )Distance operator- ()  {feet = -feet;inches = -inches;return Distance(feet, inches);}// 重载小于运算符( < )bool operator <(const Distance& d){if(feet < d.feet){return true;}if(feet == d.feet && inches < d.inches){return true;}return false;}
};
int main()
{Distance D1(11, 10), D2(5, 11);if( D1 < D2 ){cout << "D1 is less than D2 " << endl;}else{cout << "D2 is less than D1 " << endl;}return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://D2 is less than D1

C++ 输入/输出运算符重载

C++ 能够使用流提取运算符 >> 和流插入运算符 << 来输入和输出内置的数据类型。您可以重载流提取运算符和流插入运算符来操作对象等用户自定义的数据类型。

在这里,有一点很重要,我们需要把运算符重载函数声明为类的友元函数,这样我们就能不用创建对象而直接调用函数。

下面的实例演示了如何重载提取运算符 >> 和插入运算符 <<。

#include <iostream>
using namespace std;class Distance
{private:int feet;             // 0 到无穷int inches;           // 0 到 12public:// 所需的构造函数Distance(){feet = 0;inches = 0;}Distance(int f, int i){feet = f;inches = i;}friend ostream &operator<<( ostream &output, const Distance &D ){ output << "F : " << D.feet << " I : " << D.inches;return output;            }friend istream &operator>>( istream  &input, Distance &D ){ input >> D.feet >> D.inches;return input;            }
};
int main()
{Distance D1(11, 10), D2(5, 11), D3;cout << "Enter the value of object : " << endl;cin >> D3;cout << "First Distance : " << D1 << endl;cout << "Second Distance :" << D2 << endl;cout << "Third Distance :" << D3 << endl;return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://$./a.out
//Enter the value of object :
//70
//10
//First Distance : F : 11 I : 10
//Second Distance :F : 5 I : 11
//Third Distance :F : 70 I : 10

P.S:习惯上人们是使用 cin>> 和 cout<< 的,得使用友元函数来重载运算符,如果使用成员函数来重载会出现 d1<<cout; 这种不自然的代码。

C++ ++ 和 -- 运算符重载

递增运算符( ++ )和递减运算符( -- )是 C++ 语言中两个重要的一元运算符。

下面的实例演示了如何重载递增运算符( ++ ),包括前缀和后缀两种用法。类似地,您也可以尝试重载递减运算符( -- )。

#include <iostream>
using namespace std;class Time
{private:int hours;             // 0 到 23int minutes;           // 0 到 59public:// 所需的构造函数Time(){hours = 0;minutes = 0;}Time(int h, int m){hours = h;minutes = m;}// 显示时间的方法void displayTime(){cout << "H: " << hours << " M:" << minutes <<endl;}// 重载前缀递增运算符( ++ )Time operator++ ()  {++minutes;          // 对象加 1if(minutes >= 60)  {++hours;minutes -= 60;}return Time(hours, minutes);}// 重载后缀递增运算符( ++ )Time operator++( int )   //注意这里带参数!!       {// 保存原始值Time T(hours, minutes);// 对象加 1++minutes;                    if(minutes >= 60){++hours;minutes -= 60;}// 返回旧的原始值return T; }
};
int main()
{Time T1(11, 59), T2(10,40);++T1;                    // T1 加 1T1.displayTime();        // 显示 T1++T1;                    // T1 再加 1T1.displayTime();        // 显示 T1T2++;                    // T2 加 1T2.displayTime();        // 显示 T2T2++;                    // T2 再加 1T2.displayTime();        // 显示 T2return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://H: 12 M:0
//H: 12 M:1
//H: 10 M:41
//H: 10 M:42

注意,int 在 括号内是为了向编译器说明这是一个后缀形式,而不是表示整数。

前缀形式重载调用 Check operator ++ () ,后缀形式重载调用 Check operator ++ (int)。

#include <iostream>
using namespace std;class Check
{private:int i;public:Check(): i(0) {  }Check operator ++ (){Check temp;temp.i = ++i;return temp;}// 括号中插入 int 表示后缀Check operator ++ (int){Check temp;temp.i = i++;return temp;}void Display(){ cout << "i = "<< i <<endl; }
};int main()
{Check obj, obj1;    obj.Display(); obj1.Display();// 调用运算符函数,然后将 obj 的值赋给 obj1obj1 = ++obj;obj.Display();obj1.Display();// 将 obj 赋值给 obj1, 然后再调用运算符函数obj1 = obj++;obj.Display();obj1.Display();return 0;
}//执行输出结果为://i = 0
//i = 0
//i = 1
//i = 1
//i = 2
//i = 1

1、递增和递减一般是改变对象的状态,所以一般是重载为成员函数。

2、重载递增递减,一定要和指针的递增递减区分开。因为这里的重载操作的是对象,而不是指针(由于指针是内置类型,指针的递增递减是无法重载的),所以一般情况的递增递减是操作对象内部的成员变量。

3、递增和递减分为前置和后置情况,a = ++b;(前置), a = b++;(后置)。因为符号一样,所以给后置版本加一个int形参作为区分,这个形参是0,但是在函数体中是用不到的,只是为了区分前置后置。

C++ 赋值运算符重载

就像其他运算符一样,您可以重载赋值运算符( = ),用于创建一个对象,比如拷贝构造函数。

下面的实例演示了如何重载赋值运算符。

#include <iostream>
using namespace std;class Distance
{private:int feet;             // 0 到无穷int inches;           // 0 到 12public:// 所需的构造函数Distance(){feet = 0;inches = 0;}Distance(int f, int i){feet = f;inches = i;}void operator=(const Distance &D ){ feet = D.feet;inches = D.inches;}// 显示距离的方法void displayDistance(){cout << "F: " << feet <<  " I:" <<  inches << endl;}};
int main()
{Distance D1(11, 10), D2(5, 11);cout << "First Distance : "; D1.displayDistance();cout << "Second Distance :"; D2.displayDistance();// 使用赋值运算符D1 = D2;cout << "First Distance :"; D1.displayDistance();return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://First Distance : F: 11 I:10
//Second Distance :F: 5 I:11
//First Distance :F: 5 I:11

P.S:当用用户自定义类型变量向内置类型变量赋值时,可以使用自定义类型的隐式转换。

#include<iostream>
using namespace std;
class Int {
private:int n;
public:Int(int i) :n(i) {};operator int() // 这里就是隐式转换声明,应注意到它与运算符重载的不同之处{return n;}
};
int main()
{Int a(5);int c = a; // 将Int包装类对象赋值给内置int类型,隐式调用转换函数cout << c << endl;cout << a << endl; // 由于未重载Int的<<操作符,将隐式调用转换函数getchar();return 0;
}

注意:注意谨慎使用隐式转换函数,因为当你在不需要使用转换函数时,这些函数却缺可能会被调用运行;这些不正确的程序会做出一些意想不到的事情,而你又很难判断出原因。 

C++ 函数调用运算符 () 重载 

函数调用运算符 () 可以被重载用于类的对象。当重载 () 时,您不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。

下面的实例演示了如何重载函数调用运算符 () 。

#include <iostream>
using namespace std;class Distance
{private:int feet;             // 0 到无穷int inches;           // 0 到 12public:// 所需的构造函数Distance(){feet = 0;inches = 0;}Distance(int f, int i){feet = f;inches = i;}// 重载函数调用运算符Distance operator()(int a, int b, int c){Distance D;// 进行随机计算D.feet = a + c + 10;D.inches = b + c + 100 ;return D;}// 显示距离的方法void displayDistance(){cout << "F: " << feet <<  " I:" <<  inches << endl;}};
int main()
{Distance D1(11, 10), D2;cout << "First Distance : "; D1.displayDistance();D2 = D1(10, 10, 10); // 调用操作符()cout << "Second Distance :"; D2.displayDistance();return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://First Distance : F: 11 I:10
//Second Distance :F: 30 I:120

C++ 多态

多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。

C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

P.S:打个比方,猫和狗都继承自动物这个基类,动物都有“叫”这个方法,但要实现猫有猫专属的叫声,狗有狗专属的叫声,就需要用到多态。

下面的实例中,基类 Shape 被派生为两个类。

#include <iostream> 
using namespace std;class Shape {protected:int width, height;public:Shape( int a=0, int b=0){width = a;height = b;}int area(){cout << "Parent class area :" <<endl;return 0;}
};
class Rectangle: public Shape{public:Rectangle( int a=0, int b=0):Shape(a, b) { }    //调用基类构造函数int area (){ cout << "Rectangle class area :" <<endl;return (width * height); }
};
class Triangle: public Shape{public:Triangle( int a=0, int b=0):Shape(a, b) { }int area (){ cout << "Triangle class area :" <<endl;return (width * height / 2); }
};
// 程序的主函数
int main( )
{Shape *shape;Rectangle rec(10,7);Triangle  tri(10,5);// 存储矩形的地址shape = &rec;// 调用矩形的求面积函数 areashape->area();// 存储三角形的地址shape = &tri;// 调用三角形的求面积函数 areashape->area();return 0;
}//当上面的代码被编译和执行时,它会产生下列结果://Parent class area
//Parent class area

导致错误输出的原因是,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链——函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。为了解决这个问题,我们需要用到虚函数。

虚函数

虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。

我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定

现在,让我们对程序稍作修改,在 Shape 类中,area() 的声明前放置关键字 virtual。

class Shape {protected:int width, height;public:Shape( int a=0, int b=0){width = a;height = b;}virtual int area(){cout << "Parent class area :" <<endl;return 0;}
};//修改后,当编译和执行前面的实例代码时,它会产生以下结果://Rectangle class area
//Triangle class area

此时,编译器看的是指针的内容,而不是它的类型。因此,由于 tri 和 rec 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。

正如您所看到的,每个子类都有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。

P.S:多态的实现方式就是在子类中实现父类同名的方法,将子类对象的地址传给基类指针,并用基类指针调用子类和基类同名的方法,但是要记得用virtual修饰父类中的同名方法,否则会因为发生静态多态而不能得到预期的结果。

#include<iostream>
using namespace std;
class Animal {
public:virtual void cry()    //基类同名方法必须加上virtual关键字修饰{cout << "don't know how to cry." << endl;}
};
class Dog :public Animal {
public:void cry(){cout << "woof woof!" << endl;}
};
class Cat :public Animal {
public:void cry(){cout << "meow meow!" << endl;}
};
int main()
{Dog dog;Cat cat;Animal *animal;    //定义基类指针animal = &dog;    //基类指针指向子类对象doganimal->cry();    //调用Dog类中的同名方法cry()animal = &cat;    //基类指针指向子类对象catanimal->cry();    //调用Cat类中的同名方法cry()getchar();return 0;
}//执行结果:
//woof woof!
//meow meow!

纯虚函数

您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。

简单来说,虚函数可以不实现(定义)。不实现(定义)的虚函数是纯虚函数。

我们可以把基类中的虚函数 area() 改写如下:

class Shape {protected:int width, height;public:Shape( int a=0, int b=0){width = a;height = b;}// pure virtual functionvirtual int area() = 0;
};

P.S:= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。

 形成多态必须具备三个条件:

1、必须存在继承关系;

2、继承关系必须有同名虚函数(其中虚函数是在基类中使用关键字Virtual声明的函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数);

3、存在基类类型的指针或者引用,通过该指针或引用调用虚函数; 

纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。

注意:

  • 在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
  • 友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
  • 析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。

 一个类中如果存在未定义的虚函数,那么不能直接使用该类的实例,可以理解因为未定义 virtual 函数,其类是抽象的,无法实例化。将报错误。

#include<iostream>
using namespace std;
class Animal {
public:virtual void cry() = 0;
};
class Dog :public Animal {
public:void cry(){cout << "woof woof!" << endl;}
};
class Cat :public Animal {
public:void cry(){cout << "meow meow!" << endl;}
};
int main()
{Dog dog;Cat cat;Animal *animal;Animal test;    // 报错信息:不能实例化抽象类animal = &dog;animal->cry();animal = &cat;animal->cry();getchar();return 0;
}

C++ 接口(抽象类)

接口描述了类的行为和功能,而不需要完成类的特定实现。

C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。

如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 "= 0" 来指定的。

class Box    //Box就是抽象类
{public:// 纯虚函数virtual double getVolume() = 0;private:double length;      // 长度double breadth;     // 宽度double height;      // 高度
};

设计抽象类(通常称为 ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。如果试图实例化一个抽象类的对象,会导致编译错误。

因此,如果一个 ABC 的子类需要被实例化,则必须实现每个虚函数,这也意味着 C++ 支持使用 ABC 声明接口。如果没有在派生类中重写纯虚函数,就尝试实例化该类的对象,会导致编译错误。

可用于实例化对象的类被称为具体类

#include<iostream>
using namespace std;
class Animal {
public:virtual void cry() = 0;virtual void laugh() = 0;    //laugh是一个纯虚函数,但是在子类中没有被重写};
class Dog :public Animal {
public:void cry(){cout << "woof woof!" << endl;}
};
class Cat :public Animal {
public:void cry(){cout << "meow meow!" << endl;}
};
int main()
{Dog dog;Cat cat;Animal *animal;animal = &dog;animal->cry();animal = &cat;animal->cry();getchar();return 0;
}

上面的代码不能通过编译,错误提示为:

不允许使用抽象类类型 "Dog" 的对象
纯虚拟 函数 "Animal::laugh" 没有强制替代项

设计策略

面向对象的系统可能会使用一个抽象基类为所有的外部应用程序提供一个适当的、通用的、标准化的接口。然后,派生类通过继承抽象基类,就把所有类似的操作都继承下来。

外部应用程序提供的功能(即公有函数)在抽象基类中是以纯虚函数的形式存在的。这些纯虚函数在相应的派生类中被实现。

这个架构也使得新的应用程序可以很容易地被添加到系统中,即使是在系统被定义之后依然可以如此。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. Jackson

    Java的Json组件主要有Alibaba的FastJson 和 FasterXML的Jackson,比较常用的是Jackson 一、Jackson Jackson的核心类是ObjectMapper,通过ObjectMapper可以序列化和反序列化Json。 使用Jackson把Java对象转Json: 常用的有: 1、writeValue(参数一,参数二); 参数一: (1) File…...

    2024/5/9 16:31:38
  2. 苹果cms10好看的模板高权重seo友好收录好的自适应模板

    苹果cms10好看的模板高权重seo友好收录好的自适应模板应很多站长需求制做的一款强化seo版宽屏自适应视频模板,支持DIY扩展组件,各页面图文丰富频道页独立展示,多文字路径利于蜘蛛抓取,面包屑、纯文字简介等更利于排名优化(seo非神话我们不保证实际效果只能尽量优化)。声明…...

    2024/5/9 10:45:04
  3. 微信小程序第3讲--flex弹性布局基础

    flex弹性布局 相信现在大家已经学会了小程序的样式设计了,并且已经理解了视图的嵌套关系,那么今天我讲一个强大的布局:flex弹性布局 什么是布局? 布局在我们日常生活中很常见,也很容易理解。比如:我们桌面的物品怎么摆放好看?书柜里的书籍怎么摆放才更容易找到?生活中一…...

    2024/5/9 16:13:56
  4. 稀疏矩阵数据结构的存储——十字链表法

    个人理解:可以设想一个场景,一个矩阵是从左往右,从上往下,按左上角到右下角的方向生成的。在每个行和列交叉的节点上存储数据。那这些数据间有何种联系,采用何种方式可以方便的对任意节点的数据进行读写操作?假设每个节点上存储了除了数据外还有本节点的行、列标号,另外…...

    2024/4/18 5:47:55
  5. VS2012无法启动Visual Studio服务器

    最近C盘快要爆了,就自行百度看看有啥不重要的文件可以删除掉,就删除了C:\Windows\Temp和C:\Documents and Settings\Administrator\AppData\Local\Temp下的文件。做完删除动作后就看了磁盘内存的确变大了,特别的开心,然后就继续工作。结果运行VS2012的时候就有问题了,一直…...

    2024/5/3 16:40:43
  6. HSV图像与RGB图像分布代表着什么意思

    HSV H代表色色调,用角度度量,取值范围为0-360。从红色开始安逆时针方向计算,红色为0度,绿色为120度,蓝色为240度。 S代表饱和度,饱和度S表示颜色接近光谱色的程度。一种颜色,可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例越大,颜色越接近光谱色,颜色的…...

    2024/4/22 15:41:30
  7. 定义可变参数的宏

    像printf类的函数,如果代码中的比较多,且需要检查返回值时,没用到一个地方就添加一段返回值检查会比较烦,不添加,如果要求pclint扫描时会报warning 514。为了方便,有两种方式,一种是封装自己的函数,一种是定义成宏。封装成自己的函数的坏处时,函数里的输出如果使用 __…...

    2024/5/8 16:13:09
  8. 数据分析(python)箱型图分析

    箱型图 箱形图(Box-plot)用于展示一组数据的离散情况,因形状形如箱子而得名。主要用于反映原始数据分布的特征,还可以进行多组数据分布特征的比较。 异常值识别定义在小于QL-1.5IQR或者大于QL+1.5IQR的值。QL、QU分别为下四分位数,上四分位数,IQR为四分位数间距。 数据展…...

    2024/5/7 5:40:02
  9. SA533GrBCL1与SA533GrBCL2有何区别SA533GrBCL2屈服抗拉

    1、SA533GrBCL1与SA533GrBCL2有何区别 SA533GrB分两个级别SA533GrBCL1与SA533GrBCL2钢板是压力容器用淬火加回火锰钼和锰钼镍合金钢板,SA533GrBCL1钢板是美标压力容器用钢板,属于核电用容器钢板,wx188-3825-2183。SA533GrBCL1钢板是核电产业的主要用材之一,在沸水堆及压力堆…...

    2024/4/18 13:19:42
  10. 前端知识——HTML基础篇

    <!doctype html><html> <!--head标签,该标签中的内容。不会在网页中直接显示,它用来帮助解析浏览器中的页面--><head> <!--title是网页的标签,默认会显示在浏览器的标题栏中搜索引擎在检索页面时,会首先检索title标签中的内容它是网页中对于搜索…...

    2024/4/16 9:32:13
  11. Semaphore使用过吗

    Semaphore,信号灯,也叫信号量,主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制,大家想一个生活中的例子,我们开车去超市买东西的时候,首先要把车停到超市的停车场中,停车场的车位是有限的,假设停车场有3个停车位,来了6辆车,总会有3…...

    2024/4/16 9:32:33
  12. Codeforces Global Round 9E. Inversion SwapSort(构造+分解问题)⭐⭐⭐⭐

    传送门 首先你可以把所有a[i]离散化,得到相应的值(如果a中有x-1个不同的元素比这个ai小,离散值就为x) 这题的构造方法即为:从最后一个位置开始,依次往前解决所有的逆序对。 我们先考虑所有ai都不相同的简单清苦: 显然最后一个位置的值在经过调整后必须为最大的离散值n,如果…...

    2024/4/16 9:31:58
  13. hadoop 无法访问50070

    windows无法访问windows hosts文件:C:\Windows\System32\drivers\etccentos防火墙hadoop core-site.xml配置文件中的namenode地址 (yarn-site.xml RM地址)...

    2024/5/6 19:34:50
  14. Mac升级10.5.5后,Pycharm报错unKnown error,重新安装后可以使用

    pycharm报错:com.illuminatedcloud.intellij.builder.ModifiedIndicatorManagementListener.fileContentLoaded(Lcom/intellij/openapi/vfs/VirtualFile;Lcom/intellij/openapi/editor/Document;)V使用解决方法:1、删除rm -rf /Users/xinyuqing/Library/Preferences/PyCharm2…...

    2024/5/6 20:49:11
  15. 万能的淘宝 x 微信小程序 x 腾讯云 IoT 搭建居家环境监测系统实战

    本次我们基于腾讯云 IoT 物联网开发平台(IoT Explorer)和 腾讯连连 小程序,快速搭建了家庭环境检测的应用,并且能分享给家庭成员,在微信中实时查看客厅环境数据和控制家中LED灯。 硬 件 采 购为了搭建居家环境监控的 IoT 物联网场景应用,我们先来到万能的淘宝采购了如下…...

    2024/5/7 1:59:59
  16. Jpa设置默认值约束

    使用SpringDataJpa设置字段的默认值约束的2种方式 1、修改建表时的列定义属性 @Column(columnDefinition="INT DEFAULT 1") private Integer status;2、通过Hibernate(org.hibernate.annotations.ColumnDefault)下提供的注解进行设置默认值 @ColumnDefault("1&q…...

    2024/5/8 1:51:04
  17. Jpa设置默认值约束

    使用SpringDataJpa设置字段的默认值约束的2种方式 1、修改建表时的列定义属性 @Column(columnDefinition="INT DEFAULT 1") private Integer status;2、通过Hibernate(org.hibernate.annotations.ColumnDefault)下提供的注解进行设置默认值 @ColumnDefault("1&q…...

    2024/5/8 16:12:57
  18. java 获取请求url地址

    1.获取全路径 request.getRequestURL(); //得到http://localhost:8080/api/admin/login 2.获取协议名和域名 request.getScheme(); //得到协议名 例如:http request.getServerName(); //得到域名 localhost 3.获取请求所有参数 //map类型 request.getParameterMap() 4.获取…...

    2024/5/7 12:16:54
  19. java 获取请求url地址

    1.获取全路径 request.getRequestURL(); //得到http://localhost:8080/api/admin/login 2.获取协议名和域名 request.getScheme(); //得到协议名 例如:http request.getServerName(); //得到域名 localhost 3.获取请求所有参数 //map类型 request.getParameterMap() 4.获取…...

    2024/5/7 9:53:04
  20. Python之数据分析(Numpy的使用、多维数组、数据类型)

    文章目录写在前面一、数据分析与Numpy二、多维数组三、Numpy的数据类型 写在前面 代码中的np表示的是numpy,因为导入的时候是:import numpy as np 一、数据分析与Numpy 1、数据分析 MATLAB:专业化数据分析工具 Numpy:更简单上手,且结合了MATLAB大部分功能 2、Numpy介绍应用…...

    2024/4/28 0:13:22

最新文章

  1. IntelliJ IDEA 配置JDK

    IntelliJ IDEA-之配置JDK 我们的开发神器IDEA安装好了之后&#xff0c;在实际开发中&#xff0c;我们如何去配置好JDK的版本呢&#xff1f; 注意&#xff1a;需要保证JDK在已经成功安装的情况下&#xff0c;再进行IDEA的配置 现在就行动&#xff0c;让IntelliJ IDEA成为你征…...

    2024/5/9 21:07:24
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/7 10:36:02
  3. 企业常用Linux文件命令相关知识+小案例

    远程连接工具无法连接VMWARE&#xff1a; 如果发现连接工具有时连不上&#xff0c;ip存在&#xff0c;这时候我们查看网络编辑器&#xff0c;更多配置&#xff0c;看vnet8是不是10段&#xff0c;nat设置是否是正确的&#xff1f; 软件重启一下虚机还原一下网络编辑器 查看文件…...

    2024/5/9 19:03:14
  4. JRT高效率开发

    得益于前期的基础投入&#xff0c;借助代码生成的加持&#xff0c;本来计划用一周实现质控物维护界面&#xff0c;实际用来四小时左右完成质控物维护主体&#xff0c;效率大大超过预期。 JRT从设计之初就是为了证明Spring打包模式不适合软件服务模式&#xff0c;觉得Spring打包…...

    2024/5/9 17:21:26
  5. vue3项目运行正常但vscode红色波浪线报错

    以下解决办法如不生效&#xff0c;可尝试 重启 vscode 一、Vetur插件检测问题 vetur 是一个 vscode 插件&#xff0c;用于为 .vue 单文件组件提供代码高亮以及语法支持。但 vue 以及 vetur 对于 ts 的支持&#xff0c;并不友好。 1、原因 如下图&#xff1a;鼠标放到红色波浪…...

    2024/5/8 2:15:24
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/8 6:01:22
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/9 15:10:32
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/9 4:20:59
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/7 11:36:39
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/8 20:48:49
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/7 9:26:26
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/8 19:33:07
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/8 20:38:49
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/9 7:32:17
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/9 17:11:10
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57