cplusplus

cPlusPlus Tings

c++实战

static_data

// 静态数据成员,必须在外部定义和初始化,内部不能直接初始化
#include<iostream>
using namespace std;
class Point{
public:
	Point(int xx=0,int yy=0){
		X=xx;
		Y=yy;
		countP++;
	}
private:
	int X,Y;
	static int countP;
};
Point::Point(Point &p){
	X=p.xx;
    Y=p.yy;
    countP++;
}
int Point::countP=0;
// 必须在类外进行初始化,使用::指明所属类名

static_member 静态成员函数

/*
知识点:
静态成员函数
类外代码可以使用类名和作用域操作符来调用静态成员函数。
静态成员函数只能引用属于该类的静态数据成员或静态成员函数。
*/

#include <iostream>
using namespace std;
class Application {
public:
  static void f();
  static void g();

private:
  static int global;
};
int Application::global = 0;
void Application::f() { global = 5; }
void Application::g() { cout << global << endl; }

int main() {
  Application::f();
  Application::g();
  
  return 0;
}
//静态成员函数2
#include<iostream>
using namespace std;
class A{
public:
    static void f(A a);
    
private:
    int x; 
};

void A::f(A a){
   //静态成员函数只能引用属于该类的静态数据成员或静态成员函数。
  // cout<<x; //对x的引用是错误的
	cout<<a.x;//正确  通过a访问非静态数据成员
}
int main(int argc,const char*argv[])
{
    A a;
    a.f(A());
    
    return 0;
}
// 重载++的时钟.cpp
#include <iostream>
using namespace std;
class Time {
public:
  Time(int h = 0, int m = 0, int s = 0) {
    hour = h;
    minute = m;
    second = s;
  }
  Time operator++();
  Time operator++(int);
  void showTime() {
    cout << "当前时间为:" << hour << ":" << minute << ":" << second << endl;
  }

private:
  int hour, minute, second;
};
Time Time::operator++(int n){
    Time tmp=(*this);
    ++(*this);
    return tmp;
}
Time Time::operator++(){
	++second;
    if (second == 60) {
    second = 0;
    ++minute;
    if (minute == 60) {
      minute = 0;
      hour++;
      if (hour == 24) {
        hour = 0;
      }
    }
  }
  return *this;
}

int main(int argc, char const *argv[]) {
  Time t(23, 59, 59);
  ++t;
  t.showTime();
  (t++).showTime();
  t.showTime();
  
  return 0;
}
// 输出为
0:0:0
0:0:0
0:0:1

常成员函数

  • 常成员函数说明格式:类型说明符 函数名(参数表)const;
    这里,const是函数类型的一个组成部分,因此在实现部分也要带const关键字。
    const关键字可以被用于参与对重载函数的区分
    通过常对象只能调用它的常成员函数

  • 常类型的对象必须进行初始化,而且不能进行更新

class r{
    ...
    void print();
    void print()const;
    ...
}
void r::print(){...}
void r::print()const {...}//通过常对象只能调用它的常成员函数

继承多态

  • 一,类对象成员的构造: 先构造成员,再构造自身(调用构造函数)

  • 二,派生类构造函数 派生类可能有多个基类,也可能包括多个成员对象,在创建派生类对象时,派生类的构造函数除了要负责本类成员的初始化外,还要调用基类和成员对象的构造函数,并向它们传递参数,以完成基类子对象和成员对象的建立和初始化。

  • 派生类只能采用构造函数初始化列表的方式向基类或成员对象的构造函数传递参数

    • 派生类构造函数名(参数表) : 基类构造函数名(参数表) ,成员对象名1(参数表) ….{……}

    • eg:
      Line(int a,int b,int c):Point(a,b){
          len=1;
          cout<<...
      }
      
  • 三,构造函数和析构函数的调用次序 派生类对象的构造

    • 先构造基类
    • 再构造成员
    • 最后构造自身(调用构造函数)
  • 注意::基类构造顺序由派生层次决定:最远的基类最先构造 成员构造顺序和定义顺序符合 析构函数的析构顺序与构造相反

  • 构造函数和定义顺序相同,析构函数和定义相反,先调用析构函数,再删除成员,成员的删除顺序和定义顺序相反

公有私有保护继承

  • 公有继承
    • 基类中的protected成员类内部可以访问,类的使用者不可访问,类的派生类可以访问
    • 派生类不可访问基类的private成员
    • 派生类可访问基类的protected成员
    • 派生类可访问基类的public成员

  • 基类 派生类

  • public public

  • protected protected

  • private 不可访问

  • 私有继承

    • 派生类也不可访问基类的private成员
    • 基类 派生类
    • public private
    • protected private
    • private 不可访问
  • 保护继承

  • 基类的public成员在派生类中会变成protected成员, 基类的protected和private成员在派生类中保持原来的访问权限 注意点:当采用保护继承的时候,由于public成员变为protected成员,因此类的使用者不可访问!而派生类可访问!

  • 基类 派生类

  • public protected

  • protected protected

  • private 不可访问

基类和派生类对象的关系

  • 基类对象与派生类对象之间存在赋值相容性。包括以下几种情况: 把派生类对象赋值给基类对象。 把派生类对象的地址赋值给基类指针。 用派生类对象初始化基类对象的引用。 反之则不行,即不能把基类对象赋值给派生类对象;不能把基类对象的地址赋值给派生类对象的指针;也不能把基类对象作为派生对象的引用。
  • 原因:允许我们通过基类指针访问派生类对象中的基类部分。
    • 派生类对象包含了基类对象作为其一部分,因此派生类对象的地址可以被赋值给基类指针

构造函数和析构函数的构造规则

1、派生类可以不定义构造函数的情况 当具有下述情况之一时,派生类可以不定义构造函数。 基类没有定义任何构造函数。 基类具有缺省参数的构造函数。 基类具有无参构造函数。

2、派生类必须定义构造函数的情况 当基类或成员对象所属类只含有带参数的构造函数时,即使派生类本身没有数据成员要初始化,它也必须定义构造函数,并以构造函数初始化列表的方式向基类和成员对象的构造函数传递参数,以实现基类子对象和成员对象的初始化。

3、派生类的构造函数只负责直接基类的初始化

  • C++语言标准有一条规则:如果派生类的基类同时也是另外一个类的派生类,则每个派生类只负责它的直接基类的构造函数调用。 这条规则表明当派生类的直接基类只有带参数的构造函数,但没有默认构造函数时(包括缺省参数和无参构造函数),它必须在构造函数的初始化列表中调用其直接基类的构造函数,并向基类的构造函数传递参数,以实现派生类对象中的基类子对象的初始化。 这条规则有一个例外情况,当派生类存在虚基类时,所有虚基类都由最后的派生类负责初始化。

  • 注意::当有多个基类时,将按照它们在继承方式中的声明次序调用,与它们在构造函数初始化列表中的次序无关。当基类A本身又是另一个类B的派生类时,则先调用基类B的构造函数,再调用基类A的构造函数。

虚拟继承

  • 多继承下的二义性:在多继承方式下,派生类继承了多个基类的成员,当两个不同基类拥有同名成员时,容易产生名字冲突问题。

  • 虚拟继承引入的原因:重复基类,派生类间接继承同一基类使得间接基类(Person)在派生类中有多份拷贝,引发二义性。

  • 1,虚拟继承virtual inheritance 的定义 语法 class derived_class : virtual […] base_class 虚基类virtual base class 被虚拟继承的基类 在其所有的派生类中,仅出现一次

  • 2, 虚拟继承的构造次序 虚基类的初始化与一般的多重继承的初始化在语法上是一样的,但构造函数的调用顺序不同; 若基类由虚基类派生而来,则派生类必须提供对间接基类的构造(即在构造函数初始列表中构造虚基类,无论此虚基类是直接还是间接基类) 调用顺序的规定: 先调用虚基类的构造函数,再调用非虚基类的构造函数 若同一层次中包含多个虚基类,这些虚基类的构造函数按它们的说明的次序调用 若虚基类由非基类派生而来,则仍然先调用基类构造函数,再调用派生类构造函数

  • 3、虚基类由最终派生类初始化 在没有虚拟继承的情况下,每个派生类的构造函数只负责其直接基类的初始化。但在虚拟继承方式下,虚基类则由最终派生类的构造函数负责初始化。 在虚拟继承方式下,若最终派生类的构造函数没有明确调用虚基类的构造函数,编译器就会尝试调用虚基类不需要参数的构造函数(包括缺省、无参和缺省参数的构造函数),如果没找到就会产生编译错误。

class ABC : public C, public B {
public:
  //虚基类由最终派生类初始化
  ABC(int i, int j, int k)
      : C(i), B(j), A(i) // L1,这里必须对A进行初始化
  {
    cout << "Constructing ABC..." << endl;
  }
};

虚基类的调用次序

#include <iostream>
using namespace std;
class A {
  int a;

public:
  A() { cout << "Constructing A" << endl; }
};
class B {
public:
  B() { cout << "Constructing B" << endl; }
};
class B1 : virtual public B, virtual public A {
public:
  B1(int i) { cout << "Constructing B1" << endl; }
};
class B2 : public A, virtual public B {
public:
  B2(int j) { cout << "Constructing B2" << endl; }
};
class D : public B1, public B2 {
public:
  D(int m, int n) : B1(m), B2(n) { cout << "Constructing D" << endl; }
  A a;
};

int main() {
  D d(1, 2);
  
  return 0;
}

虚函数和抽象类

  • 静态绑定又称静态联编,是指在编译程序时就根据调用函数提供的信息,把它所对应的具体函数确定下来,即在编译时就把调用函数名与具体函数绑定在一起。
  • 动态绑定又称动态联编,是指在编译程序时还不能确定函数调用所对应的具体函数,只有在程序运行过程中才能够确定函数调用所对应的具体函数,即在程序运行时才把调用函数名与具体函数绑定在一起。
  • 编译时多态性: —静态联编(连接)----系统在编译时就决定如何实现某一动作,即对某一消息如何处理.静态联编具有执行速度快的优点.在C++中的编译时多态性是通过函数重载和运算符重载实现的。
  • 运行时多态性: —动态联编(连接)----系统在运行时动态实现某一动作,即对某一消息在运行过程实现其如何响应.动态联编为系统提供了灵活和高度问题抽象的优点,在C++中的运行时多态性是通过继承和虚函数实现的。
基类与派生类的赋值相容(重要)

1,派生类对象可以赋值给基类对象。 派生类对象的地址可以赋值给指向基类对象的指针。 派生类对象可以作为基类对象的引用。 赋值相容的问题: 不论哪种赋值方式,都只能通过基类对象(或基类对象的指针或引用)访问到派生类对象从基类中继承到的成员, 不能借此访问派生类定义的成员

2,虚函数使得可以通过基类对象的指针或引用访问派生类定义的成员。

3,Virtual关键字其实质是告知编译系统,被指定为virtual的函数采用动态联编的形式编译。

4,虚函数的虚特征:基类指针指向派生类的对象时,通过该指针访问其虚函数将调用派生类的版本。

  • 一旦将某个成员函数声明为虚函数后,它在继承体系中就永远为虚函数了
  • 如果基类定义了虚函数,当通过基类指针或引用调用派生类对象时,将访问到它们实际所指对象中的虚函数版本。
  • 只有通过基类对象的指针和引用访问派生类对象的虚函数时,才能体现虚函数的特性。
  • 派生类中的虚函数要保持其虚特征,必须与基类虚函数的函数原型完全相同,否则就是普通的重载函数,与基类的虚函数无关。
  • 派生类通过从基类继承的成员函数调用虚函数时,将访问到派生类中的版本。
  • 只有类的非静态成员函数才能被定义为虚函数,类的构造函数和静态成员函数不能定义为虚函数。原因是虚函数在继承层次结构中才能够发生作用,而构造函数、静态成员是不能够被继承的。
  • 内联函数也不能是虚函数。因为内联函数采用的是静态联编的方式,而虚函数是在程序运行时才与具体函数动态绑定的,采用的是动态联编的方式,即使虚函数在类体内被定义,C++编译器也将它视为非内联函数。

5.基类析构函数几乎总是为虚析构函数。 why? 假定使用delete和一个指向派生类的基类指针来销毁派生类对象,如果基类析构函数不为虚,就如一个普通成员函数,delete函数调用的就是基类析构函数。在通过基类对象的引用或指针调用派生类对象时,将致使对象析构不彻底!

抽象类的限定:

  • 抽象类中含有纯虚函数,由于纯虚函数没有实现代码,所以不能建立抽象类的对象。
  • 抽象类只能作为其他类的基类,可以通过抽象类对象的指针或引用访问到它的派生类对象,实现运行时的多态性。
  • 如果派生类只是简单地继承了抽象类的纯虚函数,而没有重新定义基类的纯虚函数,则派生类也是一个抽象类。
//基类指针或引用指向派生类对象时,虚函数与非虚函数区别:
//声明Employee的print为虚函数,则可访问到Manager的print函数,非虚函数,则只能访问到Employee的print
#include <iostream>
#include <string>
using namespace std;
class Employee {
public:
  Employee(string name, string id);
  string getName();
  string getId();
  float getSalary();
  virtual void print();

private:
  string Name;
  string Id;
};
Employee::Employee(string name, string id) {
  Name = name;
  Id = id;
}
string Employee::getName() { return Name; }
string Employee::getId() { return Id; }
float Employee::getSalary() { return 0.0; }
void Employee::print() {
  cout << "姓名:" << Name << "\t"
       << "编号:" << Id << endl;
}

class Manager : public Employee {
public:
  Manager(string name, string id, float s = 0.0) : Employee(name, id) {
    weeklySalary = s;
  }
  void setSalary(float s) { weeklySalary = s; } //设置经理的周薪
  float getSalary() { return weeklySalary; }    //获取经理的周薪
  void print() { //打印经理姓名、身份证、周薪
    cout << "经理:" << getName() << "\t\t 编号: " << getId()
         << "\t\t 周工资: " << getSalary() << endl;
  }

private:
  float weeklySalary; //周薪
};

/*
不论哪种赋值方式,都只能通过基类对象(或基类对象的指针或引用)访问到派生类对象从基类中继承到的成员,
不能借此访问派生类定义的成员。而虚函数使得可以通过基类对象的指针或引用访问派生类定义的成员。
*/
int main() {
  Employee e("小米", "NO0001"), *pM;
  Manager m("小汪", "NO0002", 128);
  m.print();
  pM = &m;
  pM->print();
  Employee &rM = m;
  rM.print();
  
  return 0;
}
// Virtual关键字其实质是告知编译系统,被指定为virtual的函数采用动态联编的形式编译。
  • 虚函数的特性:
#include <iostream>
#include <string>
using namespace std;
class A {
public:
  void f(int i) { cout << "A::f()" << endl; };
};
class B : public A {
public:
  virtual void f(int i) { cout << "B::f()" << endl; }
};
class C : public B {
public:
  void f(int i) { cout << "C::f()" << endl; }
};
//一旦将某个成员函数声明为虚函数后,它在继承体系中就永远为虚函数了
class D : public C {
public:
  void f(int) { cout << "D::f()" << endl; }
};
int main() {
  A *pA, a;
  B *pB, b;
  C c;
  D d;
  pA = &a;
  pA->f(1); //调用A::f
  pB = &b;
  pB->f(1); //调用B::f
  pB = &c;
  pB->f(1); //调用C::f
  pB = &d;
  pB->f(1); //调用D::f
  
  return 0;
}

运算符重载

二元运算符重载

aa@bb 可解释成 aa.operator@(bb) 或解释成 operator@(aa,bb)

如果两者都有定义,就按照重载解析

class X{
public:
	void operator+(int);
	X(int);
};
void operator+(X,X);
void operator+(X,double);
类运算符重载

(1)非静态成员运算符重载

以类成员形式重载的运算符参数比实际参数少一个,第1个参数是以this指针隐式传递的。

class Complex{
		double real,image;
public:
		Complex operator+(Complex b){……}
......
};

(2) 友元运算符重载

如果将运算符函数作为类的友元重载,它需要的参数个数就与运算符实际需要的参数个数相同。比如,若用友元函数重载Complex类的加法运算符,则形式如下:

class Complex{
……
		friend Complex operator+(Complex a,Complex b);		//声明
//......
};

Complex  operator+(Complex a,Complex b){……}     		//定义
重载一元运算符

1,一元运算符作为类成员函数重载时不需要参数,其形式如下:

class X{
……
		T operator@(){……};
}

T是运算符@的返回类型。从形式上看,作为类成员函数重载的一元运算符没有参数,但实际上它包含了一个隐含参数

即调用对象的this指针。

2,前自增(减)与后自增(减)

C++编译器可以通过在运算符函数参数表中是否插入关键字int 来区分这两种方式

//前缀
operator -- ();
operator -- (X & x);
//后缀
operator -- (int);
operator -- (X & x, int);
/* 计数器前后自增.cpp */
//设计一个计数器counter,用类成员重载自增运算符实现计数器的自增,用友元重载实现计数器的自减。
#include <iostream>
using namespace std;
class Counter {
private:
  int n;

public:
  Counter(int i = 0) : n(i){};
  Counter operator++();
  Counter operator++(int);
  friend Counter operator--(Counter &c);
  friend Counter operator--(Counter &c, int);
  void display();
};
Counter Counter::operator++() {
  ++n;
  return *this;
}
Counter Counter::operator++(int) {
  Counter t = *this;
  n++;
  return t;
}
Counter operator--(Counter &c) {
  --c.n;
  return c;
}
Counter operator--(Counter& c, int) {
    Counter t(c.n);
    c.n--;
    return t;
}
void Counter::display() { cout << "counter number=" << n << endl; }
int main(int argc, char const *argv[]) {
  Counter a;
  ++a;
  a.display();
  a++;
  a.display();
  --a;
  a.display();
  a--;
  a.display();
  
  return 0;
}
重载赋值运算符【】

1、[ ]是一个二元运算符,其重载形式如下:

class X{
……
		X& operator[](int n);
};

2、重载[]需要注意的问题

  • []是一个二元运算符,其第1个参数是通过对象的this指针传递的,第2个参数代表数组的下标
  • 由于[]既可以出现在赋值符“=”的左边,也可以出现在赋值符“=”的右边,所以重载运算符[]时常返回引用。
  • []只能被重载为类的非静态成员函数,不能被重载为友元和普通函数
/* []与() 运算符重载 */
#include <iostream>
using namespace std;
class X {
public:
  int operator()(int i = 0) {
    cout << "X::operator(" << i << ")" << endl;
    return i;
  };
  int operator()(int i, int j) {
    cout << "X::operator(" << i << "," << j << ")" << endl;
    return i;
  };
  int operator[](int i) {
    cout << "X::operator[" << i << "]" << endl;
    return i;
  };
  int operator[](char *cp) {
    cout << "X::operator[" << cp << "]" << endl;
    return 0;
  };
};
int main(void) {
  X obj;
  int i = obj(obj(1), 2);
  int a = obj[i];
  int b = obj["abcd"];
  cout << "a=" << a << endl;
  cout << "b=" << b << endl;
  
}
重载()

1、运算符( )是函数调用运算符,也能被重载。且只能被重载为类的成员函数。

2、运算符( )的重载形式如下:

class X{
……
		X& operator( )(参数表);
};

其中的参数表可以包括任意多个参数。

3、运算符( )的调用形式如下:

X Obj; //对象定义

Obj()(参数表); //调用形式1

Obj(参数表); //调用形式2

/* 重载()的时钟.cpp */
#include <iostream>
using namespace std;
class Time {
private:
  int hh, mm, ss;

public:
  Time(int h = 0, int m = 0, int s = 0) : hh(h), mm(m), ss(s) {}
  void operator()(int h, int m, int s) {
    hh = h;
    mm = m;
    ss = s;
  }
  void ShowTime() { cout << hh << ":" << mm << ":" << ss << endl; }
};
int main() {
  Time t1(12, 10, 11);
  t1.ShowTime();
  t1.operator()(23, 20, 34);
  t1.ShowTime();
  t1(10, 10, 10);
  t1.ShowTime();
  
}
  • 注意: 对于不要求左值且可以交换参数次序的运算符(如± /*)最好采用非成员形式(包括友元和普通函数)的重载运算符函数实现。

  • **strcpy**函数的声明如下:

char* strcpy(char* destination, const char* source);

其中,destination是要复制到的目标字符串,source是要复制的源字符串。strcpy函数会将源字符串复制到目标字符串中,直到遇到源字符串的结束符\0。需要注意的是,strcpy函数不会检查目标字符串的长度,因此需要确保目标字符串有足够的空间来存放源字符串。返回的是目标字符串的指针

  • strcmp函数的声明如下:

    int strcmp(const char* str1, const char* str2);
    

    其中,str1str2分别是要比较的两个字符串。strcmp函数会按照字典顺序比较两个字符串,如果两个字符串相等,则返回0;如果str1小于str2,则返回一个负数;如果str1大于str2,则返回一个正数。

  • substr函数是C++标准库中的一个成员函数,用于获取字符串中的子串。它接受两个参数,第一个参数是子串的起始位置(从0开始计数),第二个参数是子串的长度。如果只提供一个参数,则返回从指定位置到字符串末尾的子串

模板和STL

  • 模板分为函数模板和类模板

    • 函数模板:

      template  <class T1,class T2,...>  返回类型 函数名  (参数表)
      {
          
      }
      
      • 注意事项:

        • 1,在定义模板时,不允许template语句与函数模板定义之间有任何其他语句。

        • 2,函数模板可以有多个类型参数,但每个类型参数都必须用关键字class或typename限定。此外,模板参数中还可以出现确定类型参数,称为非类型参数。

        • template <class T1,class T2,class T3,int T4>
          T1 fx(T1 a, T 2 b, T3 c){…}	
          
        • 3,不要把这里的class与类的声明关键字class混淆在一起,虽然它们由相同的字母组成,但含义是不同的。这里的class表示T是一个类型参数,可以是任何数据类型,如int、float、char等,或者用户定义的struct、enum或class等自定义数据类型。

        • 为了区别类与模板参数中的类型关键字class,标准C++提出?了用typename作为模板参数的类型关键字,同时也支持使用class。比如,把min定义的template 写成下面的形式是完全等价的:

          template <typename T> 
          T min(T a,T b){…}
          
    • 函数模板实例化

      • 实例化发生的时机 模板实例化发生在调用模板函数时。当编译器遇到程序中对函数模板的调用时,它才会根据调用语句中实参的具体类型,确定模板参数的数据类型,并用此类型替换函数模板中的模板参数,生成能够处理该类型的函数代码,即模板函数。
      • 当多次发生类型相同的参数调用时,只在第1次进行实例化。假设有下面的函数调用:
      int x=min(2,3);     
      int y=min(3,9);
      int z=min(8.5);
      

      编译器只在第1次调用时生成模板函数,当之后遇到相同类型的参数调用时,不再生成其他模板函数,它将调用第1次实例化生成的模板函数。

    • 函数模板特化

      • 特化的原因 但在某些情况下,模板描述的通用算法不适合特定的场合(数据类型等) 比如:如max函数
      char * cp = max (“abcd”, “1234”);
      实例化为:char * max (char * a, char * b){return a > b ? a : b;}
      

      这肯定是有问题的,因为字符串的比较为:

      char * max (char * a, char * b)
      {	return strcmp(a, b)>0 ? a : b;   }
      
      • 特化 所谓特化,就是针对模板不能处理的特殊数据类型,编写与模板同名的特殊函数专门处理这些数据类型。 模板特化的定义形式: template <> 返回类型 函数名<特化的数据类型>(参数表) { …… } 说明: ① template < >是模板特化的关键字,< >中不需要任何内容; ② 函数名后的< >中是需要特化处理的数据类型。
    • 注意:当程序中同时存在模板和他的特化时,优先调用特化。 在同一个程序中,除了函数模板和它的特化外,还可以有同名的普通函数。其区别在于C++会对普通函数的调用实参进行隐式的类型转换,但不会对模板函数及特化函数的参数进行任何形式的类型转换。

    • 调用顺序 完全匹配的非模板函数, 完全匹配的模板函数 类型相容的非模板函数

  • 类模板:

    • 类模板的声明

      template<class T1,class T2,…>
      class 类名{
          	……								// 类成员的声明与定义
      }
      

      其中T1、T2是类型参数 类模板中可以有多个模板参数,包括类型参数和非类型参数

    • 非类型参数

      • 非类型参数是指某种具体的数据类型,在调用模板时只能为其提供用相应类型的常数值。非类型参数是受限制的,通常可以是整型、枚举型、对象或函数的引用,以及对象、函数或类成员的指针,但不允许用浮点型(或双精度型)、类对象或void作为非类型参数

        在下面的模板参数表中,T1、T2是类型参数,T3是非类型参数。

        template<class T1,class T2,int T3>
        
    • 类模板特化:

      • template <>  返回类型  类模板名  <特化的数据类型> :: 特化成员函数 (参数表)
        {
            
        }
        
      • eg:
        template <> void Array <char*> :: sort ()
        {
            
        }
        
    • 模板实例化发生在调用模板函数时,当编译器遇到程序中对函数模板的调用时

    • 它会根据调用语句中实参的具体类型,确定模板参数的数据类型

    • 并用此类型替换函数模板中的模板参数,生成能够处理该类型的函数代码,成为模板函数。

模板在stl中的使用
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main(int argc, char const *argv[]) {
  string name[] = {"张三", "李四", "王麻子"};
  double salary[] = {1200, 2000, 1450};
  map<string, double> sal;
  map<string, double>::iterator p;
  for (int i = 0; i < 3; i++) {
    sal.insert(make_pair(name[i], salary[i]));
  }
  sal["tom"] = 6156;
  sal["bob"] = 5999;
  for (p = sal.begin(); p != sal.end(); p++) {
    cout << p->first << "\t" << p->second << endl;
  }
  string person;
  cout << "输入查找人员的姓名:";
  cin >> person;
  int flag = 1;
  for (p = sal.begin(); p != sal.end(); p++)
    if (p->first == person) {
      cout << p->second << endl;
      flag = 0;
    }
  if (flag)
    cout << "没查找到对应的结果!" << endl;

  cout << "输入待删除的人员的姓名:";
  cin >> person;
  map<string, double>::iterator it;
  it = sal.find(person);
  if (it != sal.end()) {
    cout << "查找成功:" << (*it).first << ":" << (*it).second << endl;
    sal.erase(it);
    cout << "删除成功" << endl;
  }
  cout << "删除后的结果为" << endl;
  for (p = sal.begin(); p != sal.end(); p++) {
    cout << p->first << p->second << endl;
  }

  
  return 0;
}
  • mp和unordered_mp的不同:map时有序的,基于红黑树实现 unordered_mp是无序的,基于哈希表实现,可以在O(1)时间内查找。

异常

1,catch捕获异常时,不会进行数据类型的默认转换

2,限制异常的方法:

  • 当一个函数声明中不带任何异常描述时,它可以抛出任何异常。例如:
int f(int,char);                 //函数f可以抛出任何异常
  • 在函数声明的后面添加一个throw参数表,在其中指定函数可以抛出的异常类型。例如:
int g(int,char)  throw(int,char);  //只允许抛出int和char异常。
  • 指定throw限制表为不包括任何类型的空表,不允许函数抛出任何异常。如:
int h(int,char) throw();//不允许抛出任何异常

3,捕获所有异常 在多数情况下,catch都只用于捕获某种特定类型的异常,但它也具有捕获全部异常的能力。其形式如下:

catch(…) {
    ……                        //异常处理代码
}

4,.再次抛出异常 如是catch块无法处理捕获的异常,它可以将该异常再次抛出,使异常能够在恰当的地方被处理。再次抛出的异常不会再被同一个catch块所捕获,它将被传递给外部的catch块处理。要在catch块中再次抛出同一异常,只需在该catch块中添加不带任何参数的throw语句即可。

5,异常的嵌套调用 try块可以嵌套,即一个try块中可以包括另一个try块,这种嵌套可能形成一个异常处理的调用链。

// Eg10-2.cpp
#include <iostream>
using namespace std;
int main() {
  cout << "1--befroe try block..." << endl;
  try {
    cout << "2--Inside try block..." << endl;
    throw 10;
    cout << "3--After throw ...." << endl;
  } catch (double i) { //仅此与例10.1不同
    cout << "4--In catch block1 .. an int type is.." << i << endl;
  }
  cout << "5--After Catch...";
  
  return 0;
}
#include <iostream>
using namespace std;
void Errhandler(int n) throw() {
  try {
    if (n == 1)
      throw n;
    if (n == 2)
      throw "dx";
    if (n == 3)
      throw 1.1;
  } catch (...) {
    cout << "catch an exception..." << endl;
  }
}
int main() {
  Errhandler(1);
  Errhandler(2);
  Errhandler(3);
  
  return 0;
}

c++文件操作

  • 读写文件需要包含头文件<fstream>,其中定义了用于文件操作的类ifstream(用于读取文件)和ofstream(用于写入文件)。
打开文件
  • 使用ifstreamofstream类的对象来操作文件之前,需要先打开文件。可以使用open()方法来打开文件,指定文件名和打开模式(读取、写入、追加等)。
  • 打开文件时,可以指定文件打开模式:
    • ios::in:打开文件用于读取。
    • ios::out:打开文件用于写入。
    • ios::app:追加模式,写入的内容会添加到文件末尾。
    • ios::binary:以二进制模式打开文件。

读取文件

  • 使用ifstream对象的>>操作符或getline()方法来从文件中读取数据。
  • 可以使用eof()方法检查文件是否已经读取到末尾。

写入文件

  • 使用ofstream对象的<<操作符或write()方法来向文件中写入数据。
  • 可以使用flush()方法刷新缓冲区,确保数据被写入文件。

关闭文件

  • 使用close()方法关闭文件,释放文件资源。

检查文件是否打开成功

  • 可以使用is_open()方法检查文件是否成功打开。

处理异常

  • 在文件操作中可能会发生错误,因此建议使用异常处理机制来处理可能出现的异常。
  • 可以使用try-catch块来捕获异常,确保程序在出现错误时不会崩溃。

文本文件和二进制文件

  • 可以读写文本文件和二进制文件。读写文本文件时,数据以文本形式存储;读写二进制文件时,数据以二进制形式存储。

文件指针

  • 文件指针用于指示当前读写位置。可以使用seekg()seekp()方法来移动文件指针。

文件流对象

  • 可以使用ifstreamofstream类的对象来进行文件读写操作。
  • 也可以使用fstream类的对象进行读写操作,fstream类同时支持读取和写入。

读写CSV文件

  • CSV(逗号分隔值)文件是一种常见的文本文件格式,可以使用C++读写CSV文件进行数据处理。
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main() {
    // 打开输入文件
    ifstream inputFile("input.txt");
    if (!inputFile.is_open()) {
        cout << "Error opening input file!" << endl;
        return 1;
    }

    // 打开输出文件
    ofstream outputFile("output.txt");
    if (!outputFile.is_open()) {
        cout << "Error opening output file!" << endl;
        return 1;
    }

    // 读取输入文件内容并写入输出文件
    string line;
    while (getline(inputFile, line)) {
        outputFile << line << endl;
    }

    // 关闭文件
    inputFile.close();
    outputFile.close();

    cout << "File copied successfully!" << endl;

    return 0;
}

用异常处理机制来处理可能出现的异常。

  • 可以使用try-catch块来捕获异常,确保程序在出现错误时不会崩溃。

文本文件和二进制文件

  • 可以读写文本文件和二进制文件。读写文本文件时,数据以文本形式存储;读写二进制文件时,数据以二进制形式存储。

文件指针

  • 文件指针用于指示当前读写位置。可以使用seekg()seekp()方法来移动文件指针。

文件流对象

  • 可以使用ifstreamofstream类的对象来进行文件读写操作。
  • 也可以使用fstream类的对象进行读写操作,fstream类同时支持读取和写入。

读写CSV文件

  • CSV(逗号分隔值)文件是一种常见的文本文件格式,可以使用C++读写CSV文件进行数据处理。
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main() {
    // 打开输入文件
    ifstream inputFile("input.txt");
    if (!inputFile.is_open()) {
        cout << "Error opening input file!" << endl;
        return 1;
    }

    // 打开输出文件
    ofstream outputFile("output.txt");
    if (!outputFile.is_open()) {
        cout << "Error opening output file!" << endl;
        return 1;
    }

    // 读取输入文件内容并写入输出文件
    string line;
    while (getline(inputFile, line)) {
        outputFile << line << endl;
    }

    // 关闭文件
    inputFile.close();
    outputFile.close();

    cout << "File copied successfully!" << endl;

    return 0;
}

C++重点实战练习

中括号重载

#include<cstring>
#include<iostream>
using namespace std;
struct Person{
    double salary;
    char * name;
};

class SlarayManaege{
	Person *employ;
    int max;
    int n;  //数组中的实际职工人数
public:
    SalaryManaege (int Max=0){
		max=Max;
        n=0;
        employ = new Person[max];
    }
    //返回引用特性是可以直接放在左值,直接使用
    double & operator[] (char *Name){
        Person * p;
        for(p==employ;p<employ+n;p++)
        {
			if strcmp(p->name,Name)==0;
            	return p->salary;
        }
        //不存在情况处理
        p= employ + n++;
        p->name =new char[strlen(Name)+1];
        strcpy(p->name,Name);
        p->salary=0;
        return p->salary;
    }
      void display() {
    	for (int i = 0; i < n; i++)
      		cout << employ[i].name << "   " << employ[i].salary << endl;
  		}
    ~SalaryManaege(){delete [] employ;}
};
int main(){
	Salarymanaege  s (3) ;
    s["张三"] = 2188.88;
  	s["里斯"] = 1230.07;
  	s["王无"] = 3200.97;
  	cout << "张三\t" << s["张三"] << endl;
  	cout << "里斯\t" << s["里斯"] << endl;
 	cout << "王无\t" << s["王无"] << endl;

 	cout << "-------下为display的输出--------\n\n";
  	s.display();
    
}

时钟++运算符重载

/* 时钟++运算符重载.cpp */
#include <cmath>
#include <iostream>
using namespace std;

/*
 *  时钟类
 */
class Clock {
private:
  int Hour, Minute, Second;

public:
  Clock(int h = 0, int m = 0, int s = 0);
  void ShowTime();
  Clock &operator++();
  Clock operator++(int);
};

/*
 *  时钟类构造函数
 */
Clock::Clock(int h, int m, int s) {
  if (h >= 0 && h < 24 && m >= 0 && m < 60 && s >= 0 && s < 60) {
    Hour = h;
    Minute = m;
    Second = s;
  } else
    cout << "输入的时间格式错误!" << endl;
}

/*
 *  显示时间
 */
void Clock::ShowTime() {
  cout << Hour << ":" << Minute << ":" << Second << endl;
}

/*
 *  时间递增一秒(重载前缀++运算符)
 */
Clock &Clock::operator++() {
  Second++;
  if (Second >= 60) {
    Second = Second - 60;
    Minute++;
    if (Minute >= 60) {
      Minute = Minute - 60;
      Hour++;
      Hour = Hour % 24;
    }
  }
  return *this;
}

/*
 *  时间递增一秒(重载后缀++运算符)
 */
Clock Clock::operator++(int) {
  Clock old = *this;
  ++(*this);
  return old;
}

/*
 *  主函数
 */
int main() {
  Clock myClock(23, 59, 59);
  cout << "初始化显示时间为:\t\t";
  myClock.ShowTime();

  cout << "执行myClock++后的时间为:\t";

  //先执行ShowTime(),输出myClock=23:59:59,
  //再执行myClock++,此时myClock=00:00:00
  (myClock++).ShowTime();

  cout << "执行++myClock后的时间为:\t";

  //先执行++myClock,此时myClock=00:00:01
  //再执行ShowTime(),输出myClock=00:00:01
  (++myClock).ShowTime();
  
}

运算符重载之强制转换

/*
有一个类Circle,设计该类的类型转换函数,当将Circle对象转换成int型时,返回圆的半径;当将它转换成double型时,就返回圆的周长;当将它转换成float型时,就返回圆的面积。
*/

/*

类型转换函数没有参数。
类型转换函数没有返回类型。
类型转换函数必须返回将要转换成的type类型数据。

*/

#include <iostream>
using namespace std;
class Circle {
private:
  double x, y, r;

public:
  Circle(double x1, double y1, double r1) {
    x = x1;
    y = y1;
    r = r1;
  }
  operator int() { return int(r); }
  operator double() { return 2 * 3.14 * r; }
  operator float() { return (float)3.14 * r * r; }
};
int main() {
  Circle c(2.3, 3.4, 2.5);
  int r = c;         //调用operator int(),将Circle类型转换成int
  double length = c; //调用operator double(),转换成double
  float area = c;    //调用operator float(),将Circle类型转换成float
  double len = (double)c; //将Cirlce类型对象强制转换成double
  cout << r << endl;
  cout << length << endl;
  cout << len << endl;
  cout << area << endl;
  
}

重载圆括号的时钟

#include <iostream>
using namespace std;
class Time {
private:
  int hh, mm, ss;

public:
  Time(int h = 0, int m = 0, int s = 0) : hh(h), mm(m), ss(s) {}
  void operator()(int h, int m, int s) {
    hh = h;
    mm = m;
    ss = s;
  }
  void ShowTime() { cout << hh << ":" << mm << ":" << ss << endl; }
};
int main() {
  Time t1(12, 10, 11);
  t1.ShowTime();
  t1.operator()(23, 20, 34);
  t1.ShowTime();
  t1(10, 10, 10);
  t1.ShowTime();
  
}

函数模板

#include <cstring>
#include <iostream>
using namespace std;

template <class T> T compareMax(T t1, T t2) { return t1 > t2 ? t1 : t2; }

template <>
const char *compareMax<const char *>(const char *s1, const char *s2) {  //模板特化
  cout << "[for debug]"
       << " call compareMax template" << endl;
  return strcmp(s1, s2) >= 0 ? s1 : s2;
}

int main(int argc, char const *argv[]) {
  cout << compareMax(1, 2) << endl;
  cout << compareMax("asda", "qweq") << endl;
  
  return 0;
}
  • 类模板特化

  • template <>  返回类型  类模板名  <特化的数据类型> :: 特化成员函数 (参数表)
    {
        
    }
    

动态数组

#include<cstring>
#include<iostream>

int main()
{
	char * sPtr;
    const char* s ="hello";
    sPtr=new char[strlen(s)+1];
    strncpy(sPtr,s,strlen(s));
    std::cout<<sPtr<<std::endl;
    delete sPtr;
    return 0;
}

需要注意的是,在使用动态数组时,需要手动释放内存以避免内存泄漏问题。可以使用delete[]来释放动态数组占用的内存。
  • strncpy函数用于将一个字符串一部分复制到另一个字符串中

    • char* strncpy(char* dest,const char* src,size_t n);
      dest 是目标字符串指针
      src 是源字符串指针
      

字典的插入和查找

/* 字典插入与查找.cpp */
#include <cstring>
#include <iostream>
#include <map>
using namespace std;

int main(int argc, char const *argv[]) {
  map<const char *, const char *> mp;
  map<const char *, const char *>::iterator iter;
  const char key[3][20] = {"img", "system", "ip"};
  const char value[3][20] = {"d:/a.img", "win7", "193.68.6.3"};

  // make_pair插入
  for (int i = 0; i < 2; i++) {
    mp.insert(make_pair(key[i], value[i]));
  }
  // pair<const char*,const char*>插入
  mp.insert(pair<const char *, const char *>(key[2], value[2]));

  //数组插入方式
  mp["addr"] = "中国";
  //迭代器取出元素
  //循环取出元素
  for (iter = mp.begin(); iter != mp.end(); iter++) {
    cout << iter->first << "\t" << iter->second << endl;
  }
  char key1[20];
  cout << "请输入按key查找:";
  cin.getline(key1, 20);
  //查找元素
  for (iter = mp.begin(); iter != mp.end(); iter++) {
    if (strcmp(iter->first, key1) == 0) {
      cout << iter->first << "查找出来了!"
           << "对应的值为:" << iter->second << endl;
    }
  }

  //第一种删除方式
  // find只能用于查找数组建立的形式!并且不支持输入用cin等
  iter = mp.find("addr");
  if (iter != mp.end()) {
    cout << iter->first << "按照key查找出来了!"
         << "对应的value为:" << iter->second << endl;
    cout << "开始删除元素!" << endl;
    mp.erase(iter);
    // break;
  }
  //第二种方式删除
  //按照key删除元素
  char drop_key[20];
  //按照value删除元素
  char drop_value[20];
  cout << "请输入按key删除:";
  cin.getline(drop_key, 20);
  cout << "请输入按value删除:";
  cin.getline(drop_value, 20);
  for (iter = mp.begin(); iter != mp.end(); iter++) {
    if (strcmp(iter->first, drop_key) == 0) {
      cout << iter->first << "按照key查找出来了!"
           << "对应的value为:" << iter->second << endl;
      cout << "开始删除元素!" << endl;
      mp.erase(iter);
      break;
    }
    if (strcmp(iter->second, drop_value) == 0) {
      cout << iter->second << "value查找出来了!"
           << "对应的key为:" << iter->first << endl;
      cout << "开始删除元素!" << endl;
      mp.erase(iter);
      break;
    }
  }
  cout << "------删除元素以后--------\n";
  //循环取出元素
  for (iter = mp.begin(); iter != mp.end(); iter++) {
    cout << iter->first << "\t" << iter->second << endl;
  }
  
  return 0;
}

make_pair  是一个函数模板  用于创建一个std::pair对象,即一个包含两个值的有序对。std::pair是一个STL容器,可以存储两个不同类型的值。

make_pair函数的语法如下:

template <class T1, class T2>
std::pair<T1, T2> make_pair(T1 t1, T2 t2);

异常捕获

/* 异常捕获.cpp */
#include <iostream>
using namespace std;
//函数异常可以抛出去由主函数来处理
void fun(int x) {
  try {
    if (x == 0)
      throw "异常";
  } catch (...) {
    cout << "in fun" << endl;
    throw 1;
  }
}

int main(int argc, char const *argv[]) {

  try {
    fun(0);
  } catch (int n) {
    cout << "in main" << endl;
  }

  
  return 0;
}

类模板之栈

#include<iostream>
using namespace std;
template<typename T,int MAXSIZE>

class Stack{
    public:
    	Stack(){};
    	void init(){top==-1};
    	bool isFull(){
			if(top==MAXSIZE-1)
                return true;
            else
                return false;
        }
    	bool isEmpty(){
            if(top==-1)
                return true;
            else
                return false;
        }
    	void push(T e);
    	T pop();
    
    
    
    private:
    	T elems[MAXSIZE];
    	int top;
};
template <typename T,int MAXSIZE> void Stack<T ,MAXSIZE>::push(T e){
	if(!isFull()){
		elems[++top]=e;
    }else{
		cout<<“栈满了”;
        return;
    }
}
template <typename T, int MAXSIZE> T Stack<T, MAXSIZE>::pop() {
  if (!isEmpty()) {
    return elems[top--];
  } else {
    cout << "栈已空,请不要再弹出元素!";
    return 0;
  }
}

int main(int argc, char const *argv[]) {
  Stack<int, 10> s1;
  s1.init();
  int i;
  for (i = 1; i < 11; i++)
    s1.push(i);
  for (i = 1; i < 11; i++)
    cout << s1.pop() << "\t";
  cout << endl;
  
  return 0;
}

类模板化之数组

/* 类模板特化之数组.cpp */
#include <cstring>
#include <iostream>
using namespace std;
#define MAXSIZE 5
template <class T> class Array {
public:
  Array() {
    for (int i = 0; i < MAXSIZE; i++) {
      array[i] = 0;
    }
  }

  T &operator[](int i);
  void Sort();

private:
  T array[MAXSIZE];
};

template <class T> T &Array<T>::operator[](int i) {
  if (i < 0 || i > MAXSIZE - 1) {
    cout << "数组下标越界!" << endl;
    exit(0);
  }
  return array[i];
}

template <class T> void Array<T>::Sort() {
  int p, j;
  for (int i = 0; i < MAXSIZE - 1; i++) {
    p = i;
    for (j = i + 1; j < MAXSIZE; j++) {
      if (array[p] < array[j])
        p = j;
    }
    T t;
    t = array[i];
    array[i] = array[p];
    array[p] = t;
  }
}
template <> void Array<char *>::Sort() {
  int p, j;
  for (int i = 0; i < MAXSIZE - 1; i++) {
    p = i;
    for (j = i + 1; j < MAXSIZE; j++) {
      if (strcmp(array[p], array[j]) < 0)
        p = j;
    }
    char *t = array[i];
    array[i] = array[p];
    array[p] = t;
  }
}

int main(int argc, char const *argv[]) {
  Array<int> a1;
  Array<char *> b1;
  a1[0] = 1;
  a1[1] = 23;
  a1[2] = 6;
  a1[3] = 3;
  a1[4] = 9;
  a1.Sort();
  for (int i = 0; i < 5; i++)
    cout << a1[i] << "\t";
  cout << endl;
  b1[0] = "x1";
  b1[1] = "ya";
  b1[2] = "ad";
  b1[3] = "be";
  b1[4] = "bc";
  b1.Sort();
  for (int i = 0; i < 5; i++)
    cout << b1[i] << "\t";
  cout << endl;
  
  return 0;
}

继承与封装

/* 继承与封装.cpp */
#include <cstring>
#include <iostream>

using namespace std;
class Employee {
public:
  Employee(const char *name, const char *id) {
    strcpy(Name, name);
    strcpy(Id, id);
  }
  char *getName() { return Name; }
  char *getId() { return Id; }
  void display() { cout << Name << "\t" << Id << endl; }

private:
  char Name[20];
  char Id[20];
};

class Manager : public Employee {
public:
  //直接调用构造方法传递,基类构造方法有参数,派生类必须通过构造方法,在初始化列表中传递参数
  Manager(const char *name, const char *id, int week) : Employee(name, id) {
    WeeklySalary = week * 1000;
  }
  void display() {
    cout << "经理:" << getName() << "\t" << getId() << "\t" << WeeklySalary
         << endl;
  }

private:
  int WeeklySalary;
};

class SaleWorker : public Employee {
public:
  SaleWorker(const char *name, const char *id, int profit, int x)
      : Employee(name, id) {
    workerMoney = baseMoney + x * 0.05 * profit;
  }
  void display() {
    cout << "销售员:" << getName() << "\t" << getId() << "\t" << workerMoney
         << endl;
  }

private:
  float baseMoney = 800.0;
  float workerMoney;
};
class HourWorker : public Employee {
public:
  HourWorker(const char *name, const char *id, int h) : Employee(name, id) {
    TotalMoney = h * hourMoney;
  }
  void display() {
    cout << "小时工:" << getName() << "\t" << getId() << "\t" << TotalMoney
         << endl;
  }

private:
  float hourMoney = 100.0;
  float TotalMoney;
};

int main(int argc, char const *argv[]) {
  cout << "请输入工作周:";
  int week;
  cin >> week;
  Manager m("小王", "11111111", week);
  m.display();
  cout << "请输入销售利润:";
  int profit;
  cin >> profit;
  cout << "请输入销售件数:";
  int x;
  cin >> x;
  SaleWorker s("小李", "222222", profit, x);
  s.display();
  cout << "请输入工作小时:";
  int hour;
  cin >> hour;
  HourWorker h("小何", "333333", hour);
  h.display();
  
  return 0;
}

文件读写

/* 读写文件综合题.cpp */
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
using namespace std;

class Person {
public:
  Person() {}
  Person(char *name, char *id, int math, int chinese, int english) {
    strcpy(Name, name);
    strcpy(Id, id);
    Math = math;
    Chinese = chinese;
    English = english;
    Sum = Math + Chinese + English;
  }

  void display() {
    cout << Name << "\t" << Id << "\t" << Math << "\t" << Chinese << "\t"
         << English << "\t" << Sum << endl;
  }

private:
  char Name[20];
  char Id[20];
  int Math;
  int Chinese;
  int English;
  int Sum;
};

int main(int argc, char const *argv[]) {
  char ch;
  char Name[20], Id[20];
  int Math, Chinese, English;
  fstream ioFile;
  ioFile.open("d:/per.dat", ios::out | ios::app);
  cout << "---------建立学生档案信息----------\n";
  do {
    cout << "请输入姓名:";
    cin >> Name;
    cout << "请输入身份证号:";
    cin >> Id;
    cout << "请输入数学成绩:";
    cin >> Math;
    cout << "请输入汉语成绩:";
    cin >> Chinese;
    cout << "请输入英语成绩:";
    cin >> English;
    Person per(Name, Id, Math, Chinese, English);
    ioFile.write((char *)&per, sizeof(per));
    cout << "您是否继续建档?(Y/y)  ";
    cin >> ch;
  } while (ch == 'y' || ch == 'Y');
  ioFile.close();
  ioFile.open("d://per.dat", ios::in);
  Person p;
  ioFile.read((char *)&p, sizeof(p));
  vector<Person> v;
  vector<Person>::iterator vt;
  while (!ioFile.eof()) {
    v.push_back(p);
    ioFile.read((char *)&p, sizeof(p));
  }
  ioFile.close();
  cout << "---------输出档案信息-----------" << endl;
  for (vt = v.begin(); vt != v.end(); vt++) {
    (*vt).display();
  }
  
  return 0;
}

输入输出运算符重载

/* 输入输出运算符重载.cpp */
/*
   有一销售人员类Sales,其数据成员有姓名name,身份证号id,年龄age。
   重载输入/输出运算符实现对Sales类数据成员的输入和输出。
 */
#include <cstring>
#include <iostream>
using namespace std;
class Sales {
private:
  char name[10];
  char id[18];
  int age;

public:
  Sales(char *Name, char *ID, int Age);

  friend Sales &operator<<(ostream &os, Sales &s); //重载输出运算符
  friend Sales &operator>>(istream &is, Sales &s); //重载输入运算符
};
Sales::Sales(char *Name, char *ID, int Age) {
  strcpy(name, Name);
  strcpy(id, ID);
  age = Age;
}
Sales &operator<<(ostream &os, Sales &s) {
  os << s.name << "\t"; //输出姓名
  os << s.id << "\t";   //输出身份证号
  os << s.age << endl;  //输出年龄
  return s;
}
Sales &operator>>(istream &is, Sales &s) {
  cout << "输入雇员的姓名,身份证号,年龄" << endl;
  is >> s.name >> s.id >> s.age;
  return s;
}
int main() {
  Sales s1("杜康", "214198012111711", 40); // L1
  cout << s1;                              // L2
  cout << endl;                            // L3
  cin >> s1;                               // L4
  cout << s1;                              // L5
}

输入输出重载

/* 输入输出重载.cpp */
#include <iostream>
using namespace std;
#include <cstring>
class Sales {
private:
  char name[10];
  char id[18];
  int age;

public:
  Sales(char *Name, char *ID, int Age);
  friend Sales &operator<<(ostream &os, Sales &s);
  friend Sales &operator>>(istream &is, Sales &s);
};

Sales::Sales(char *Name, char *ID, int Age) {
  strcpy(name, Name);
  strcpy(id, ID);
  age = Age;
}

Sales &operator<<(ostream &os, Sales &s) {
  os << s.name << "\t" << s.id << "\t" << s.age << endl;
  return s;
}

Sales &operator>>(istream &is, Sales &s) {
  cout << "输入雇员的姓名,身份证,年龄:\n";
  is >> s.name >> s.id >> s.age;
}

int main(int argc, char const *argv[]) {
  Sales s("张三", "15611", 26);
  cout << s;
  cin >> s;
  cout << s;
  
  return 0;
}

输出格式

/* 输出格式.cpp */
#include <iomanip>
#include <iostream>
using namespace std;

int main(int argc, char const *argv[]) {
  char s[20] = "this is a string";
  double digit = -36.96656;
  cout << setw(30) << left << setfill('*') << s << endl;
  cout << dec << setprecision(4) << digit << endl;
  cout << dec << 15 << endl;
  // setbase(int x)设置进制后,后面所有操作都是按照这个进制来计算!
  cout << setbase(10) << 15 << endl;
  //四舍五入,并保留2位有效数组
  float x = 6.6937;
  cout << float(int(x * 1000 + 0.5) / 1000.0) << endl;
  
  return 0;
}

	setw:setw是C++中的一个流控制器,用于设置输出的字段宽度。通过setw可以指定输出的字段宽度,不足的部分用空格填充。
    setfill:setfill是C++中的一个流控制器,用于设置填充字符。通过setfill可以指定在setw设置的字段宽度不足时,用什么字符填充。
    setprecision:setprecision是C++中的一个流控制器,用于设置输出浮点数的精度。通过setprecision可以指定浮点数输出的小数位数。
        left表示输出的数据在指定宽度内左对齐。例如,cout << left << setw(10) << "Hello"; 将输出"Hello"并在其后填充空格使总宽度为10。
        dec表示输出的整数以十进制形式输出。默认情况下,整数会以十进制形式输出,但可以使用dec来明确指定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值