【C++】多态,要这样学

news2024/11/14 14:22:28
头像
🚀个人主页:奋斗的小羊
🚀所属专栏:C++
很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~

动图描述

目录

  • 前言
  • 💥1、多态的定义和实现
    • 💥1.1 什么是多态
    • 💥1.2 多态的定义和实现
      • 💥1.2.1 虚函数
      • 💥1.2.2 构成多态的条件
      • 💥1.2.3 虚函数的重写(覆盖)
      • 💥1.2.4 为什么要重写析构函数
      • 💥1.2.5 c++11 override 和 final
      • 💥1.2.6 重载、重写(覆盖)、隐藏(重定义)
    • 💥1.3 抽象类
      • 💥1.3.1 定义
      • 💥1.3.2 实现继承和接口继承
  • 💥2、多态的原理
    • 💥2.1 虚函数表
    • 💥2.2 多态的原理
    • 💥2.3 动态绑定和静态绑定
  • 总结收尾


前言

本篇文章带你深入学习面向对象设计思想的重要体现之一——多态。
多态在面向对象编程(OOP)中具有深远的意义,它不仅是OOP的三大特性之一,还是实现代码复用、提高程序灵活性和可扩展性的重要手段。


💥1、多态的定义和实现

💥1.1 什么是多态

简单来说多态就是多种形态,细说就是当不同的对象去做同一个行为,得到的结果不同。
多态是面向对象编程中的一个核心概念,它允许我们以统一的接口来操作不同的对象。多态意味着 “多种形态”,即多种表现形式或类型。在编程中,多态通常指的是一个接口(或基类)可以有多种实现方式,或者一个对象可以在不同的情境下表现出不同的行为。

比如: 扫码支付,同样是扫码,当扫微信二维码是使用的是微信支付,当扫支付宝二维码时使用的是支付宝支付等。

在这里插入图片描述


💥1.2 多态的定义和实现

💥1.2.1 虚函数

virtual修饰的类成员函数就是虚函数。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "全价买票" << endl;
	}
};

💥1.2.2 构成多态的条件

两个条件缺一不可。

  1. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数重写(实现)
  2. 必须是基类的指针或引用调用虚函数

多态是在不同继承关系的类对象去调用同一个函数,产生了不同的行为。
比如同样买火车票,普通人是全价,学生是半价,军人的话是优先买票。这就是同一件事不同人去做得到的结果就不一样。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "全价买票" << endl;
	}
};
class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "半价买票" << endl;
	}
};
class Soldier : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "优先买票" << endl;
	}
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person pe;
	Func(pe);

	Student st;
	Func(st);

	Soldier so;
	Func(so);
	return 0;
}

💥1.2.3 虚函数的重写(覆盖)

派生类中有一个和基类完全相同的虚函数(函数名、参数列表、返回类型都相同),称子类的虚函数重写(也叫覆盖)了基类的虚函数。

  • 派生类的虚函数不加virtual,也可以构成重写(继承后基类的虚函数被继承下来了),但规范起见还是不建议省略
  • 虽然虚函数的实现可以重写,但接口只有一个

以下程序的输出结果是什么?

class A
{
public:
	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
	virtual void test() { func(); }
};

class B : public A
{
public:
	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};

int main(int argc, char* argv[])
{
	B* p = new B;
	p->test();
	return 0;
}

上面的程序输出结果是:B->1

指针p指向B类类型对象,调用由A类继承下来的test函数,在test函数中再调用func函数,调用的是B类中的func函数,先输出“B->”,再打印后面的val,这道题最关键的点就是这里打印的val是A类中func函数参数列表中的val,还是B类中func函数参数列表中的val。
前面我们说过,多态是以统一的接口去操作不同的对象,所以这里即使B类中重写了A类中的func函数,但重写只是重写了函数的实现,接口用的还是统一的,所以这里虽然执行的是B类中的func函数,但val用的还是A类中func函数参数列表中的val

虚函数重写的两个例外:

  1. 协变(了解)
    派生类重写基类虚函数时,与基类虚函数的返回类型不同。即基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或引用时,称为协变。
class A {};
class B : public A {};

class Person
{
public:
	virtual A* func() { return new A; }
};
class Student : public Person
{
public:
	virtual B* func() { return new B; }
};
  1. 析构函数的重写
    如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写。在继承一文中我们提到过,因为多态中一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destructor()
class Person
{
public:
	virtual ~Person() { cout << "~Person" << endl;}
};
class Student : public Person
{
public:
	virtual ~Student() { cout << "~Student" << endl; }
};

int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;

	delete p1;
	delete p2;
	return 0;
}
  • 只有派生类的析构函数重写了基类的析构函数,delete对象调用析构函数才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数

💥1.2.4 为什么要重写析构函数

普通情况下析构子类对象:

class Person
{
public:
	~Person() { cout << "~Person" << endl;}
};
class Student : public Person
{
public:
	~Student() { cout << "~Student" << endl; }
};

int main()
{
	Student s;
	return 0;
}

在这里插入图片描述

我们知道子类对象析构清理先调用子类析构再调用父类析构,没什么问题。而当我们通过父类指针删除子类对象时,会出现问题:

class Person
{
public:
	~Person() { cout << "~Person" << endl;}
};
class Student : public Person
{
public:
	~Student() { cout << "~Student" << endl; }
};

int main()
{
	Person* p = new Student;
	delete p;
	return 0;
}

在这里插入图片描述

我们看到指针p指向的是子类对象,但最后却只调用了基类的析构函数,我们期望的是调用子类的析构函数,这里用多态处理,就可以正常了。

class Person
{
public:
	virtual ~Person() { cout << "~Person" << endl;}
};
class Student : public Person
{
public:
	virtual ~Student() { cout << "~Student" << endl; }
};

int main()
{
	Person* p = new Student;
	delete p;
	return 0;
}

请添加图片描述

从这里我们可以知道,普通调用多态调用的区别,普通调用看调用者类型,多态调用看指向对象的类型

析构函数是一个特殊的成员函数,它在对象的生命周期结束时自动被调用,用于执行清理工作,如释放对象所占用的资源。在某些情况下,我们可能需要重写基类中的析构函数:

  1. 资源管理:如果基类负责管理某些资源(如动态分配的内存、文件句柄等),而派生类需要扩展或修改这些资源的管理方式,那么派生类需要重写析构函数来确保这些资源被正确释放。
  2. 多态删除:在使用多态时(即基类指针指向派生类对象),如果通过基类指针删除派生类对象,并且基类析构函数没有被声明为虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类对象中的资源没有被正确释放,从而造成内存泄漏等问题。因此,在多态基类中,通常会将析构函数声明为虚函数,并可能需要在派生类中重写它以执行特定的清理工作。
  3. 依赖关系:如果派生类依赖于基类析构函数的某些行为(比如基类析构函数中的某些资源释放逻辑),但需要在这些操作之前或之后执行额外的操作,那么派生类需要重写析构函数来实现这一点。
  4. 异常安全:在析构函数中处理异常需要特别小心,因为异常在析构函数中抛出时可能导致程序异常终止(除非在析构函数中捕获了所有异常)。如果基类析构函数中有可能导致异常的代码,并且派生类需要以一种特殊的方式处理这些异常,那么派生类可能需要重写析构函数来提供异常安全的清理逻辑。

需要注意的是,即使你不需要在派生类的析构函数中执行任何特定的清理工作,如果你打算通过基类指针来删除派生类对象,并且想要确保派生类对象中的资源被正确释放,你也应该将基类的析构函数声明为虚函数。这样做可以确保当通过基类指针删除派生类对象时,派生类的析构函数也会被调用。


💥1.2.5 c++11 override 和 final

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了overridefinal两个关键字,可以帮助用户检测是否重写。

  1. final:修饰虚函数,表示该虚函数不能被重写
class Person
{
public:
	virtual ~Person() final { cout << "~Person" << endl;}
};
class Student : public Person
{
public:
	virtual ~Student() { cout << "~Student" << endl; }
};
  1. override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写则编译报错
class Person
{
public:
	virtual ~Person()  { cout << "~Person" << endl;}
};
class Student : public Person
{
public:
	virtual ~Student() override { cout << "~Student" << endl; }
};

💥1.2.6 重载、重写(覆盖)、隐藏(重定义)

  • 重载: 两个函数在同一作用域,函数名相同,参数不同
  • 重写: 两个函数分别在基类和派生类的作用域,函数名、参数、返回类型都相同(协变除外),两个函数必须是虚函数
  • 隐藏: 两个函数分别在基类和派生类的作用域,函数名相同,两个基类和派生类的同名函数不构成重写就是隐藏

重写也可以调用隐藏,重写是一种特殊的隐藏。


💥1.3 抽象类

💥1.3.1 定义

虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:
	virtual void Drive() = 0;
};
class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};
class BMW :public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW-操控" << endl;
	}
};
void Test()
{
	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();
}

抽象类在哪些场景下会使用呢?
假设有一个动物系统,其中包含多种动物如狗、猫、鸟等。这些动物都具有一些共同的行为,如吃和睡。此时,可以定义一个动物抽象类,其中包含eat和sleep方法的声明(其中eat可能为抽象方法,因为不同动物的吃法可能不同;而sleep方法则可能已经在Animal类中给出了具体实现)。然后,狗、猫、鸟等类继承自Animal类,并实现各自的eat方法。
抽象类是实现多态的一种重要手段。通过抽象类和接口,可以实现父类类型的引用指向子类对象,调用方法时根据对象的实际类型执行相应的实现。这种方式可以增加程序的灵活性和可扩展性。


💥1.3.2 实现继承和接口继承

  1. 实现继承
    普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数实现。
  2. 接口继承
    虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,实现多态,继承的是接口。
    如果不实现多态,不要把函数定义成虚函数。

💥2、多态的原理

💥2.1 虚函数表

我们通过一道例题来初步认识虚函数表。
在下面的代码中,sizeof(b)的值是多少?

class Base
{
public:
	virtual void func()
	{
		cout << "func()" << endl;
	}
private:
	int _a = 1;
};

int main()
{
	Base b;
	
	return 0;
}

在不了解虚函数表前,我们可能会根据以往的知识判断sizeof(b)的值是4,事实上sizeof(b)的值是8(32位环境下)。

请添加图片描述

通过观察我们发现b对象的大小是8字节,除了_a成员,还多一个__vfptr放在对象的前面(有些平台可能会放到对象的最后面),__vfptr是一个指针(函数指针),我们叫做虚函数表指针(v代表virtua,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。那么派生类中这个表放了些什么呢?

class Base
{
public:
	virtual void func1()
	{
		cout << "func1()" << endl;
	}
	virtual void func2()
	{
		cout << "func2()" << endl;
	}
	void func3()
	{
		cout << "func3()" << endl;
	}
private:
	int _a = 1;
};

class Derive : public Base
{
public:
	virtual void func1()
	{
		cout << "Derive::func1()" << endl;
	}
private:
	int _b = 1;
};

int main()
{
	Base b;
	Derive d;
	return 0;
}

上面我们在Base中继续增加了一个虚函数和一个普通函数,然后让Derive继承Base,在Derive中只重写func1函数。

请添加图片描述

通过观察,可以总结出:

  1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
  2. 基类b对象和派生类d对象虚表是不一样的,这里我们发现func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
  3. 另外func2继承下来后是虚函数,所以放进了虚表,func3也继承下来了,但是不是虚函数,所以不会放进虚表。
  4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr
  5. 派生类的虚表生成是:先将基类中的虚表内容拷贝一份到派生类虚表中,如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数,派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
  6. 当通过基类指针或引用调用虚函数时,程序会首先查找该指针所指向的虚函数表,然后根据表中存储的地址来调用相应的函数。

| 最后有一个问题:虚函数存在哪里?虚表存在哪里?

虚函数不是存在虚表,虚表也不是存在对象中。
虚表存的只是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪里?VS下虚表是存在代码段的。


💥2.2 多态的原理

这里我们再简单总结一下多态的原理:

  1. 多态是不同对象去完成同一个行为,展现出不同的形态
  2. 多态有两个必须条件:虚函数覆盖和对象的指针或引用调用虚函数
  3. 对象切片: 需要注意的是,多态性仅适用于通过基类指针或引用来调用虚函数。如果尝试将派生类对象赋值给基类对象(非引用或指针),则会发生对象切片,即只保留基类部分的数据,派生类特有的部分会被丢弃。这会导致多态性的失效。
  4. 满足多态以后的函数调用,不是在编译时确定的,是运行起来后到对象中去找的,不满足多态的函数调用是编译时确定好的。
  5. 当通过基类指针或引用调用虚函数时,会根据指针或引用实际指向的对象的类型来调用相应的函数版本,这称为动态绑定。

在这里插入图片描述


💥2.3 动态绑定和静态绑定

  1. 静态绑定,也称为早期绑定,指的是函数调用在编译时期就已经确定。这种绑定方式主要适用于非虚函数,比如函数重载。
  2. 动态绑定,也称为晚期绑定,是指函数调用在运行时确定。这通常是通过虚函数机制来实现的。当一个函数在基类中被声明为虚函数后,派生类可以重写这个函数,创建自己的实现。如果通过基类的指针或引用调用这个函数,那么实际调用的版本将根据对象的实际类型(运行时的类型)来确定。

总结收尾

  • 多态允许使用统一的接口来处理不同的对象类型,这意味着我们可以编写能够处理多种类型的通用代码,而无需为每种类型编写特定的代码。这种能力极大地提高了代码的复用性,减少了重复代码,使得程序更加简洁和易于维护。
  • 多态是面向对象编程中一个非常强大的特性,它使得我们能够编写出更加模块化、灵活和可扩展的代码。然而,使用多态时也需要注意一些潜在的问题,比如虚函数调用的性能开销和虚析构函数的正确使用等。

本篇文章的学习就到这了,如果您觉得在本文中有所收获,还请留下您的三连支持哦~

头像

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2140372.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

杨氏矩阵中查找想要找到的数

文章目录 一、题目二、思路三、代码实现 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、题目 二、思路 第一步 根据杨氏矩阵的规则说明矩阵从左到右递增&#xff0c;从上往下递增&#xff0c;因此我们可以画出这样的图。 对于杨氏矩阵&#xff0…

java面向对象:构造方法

给出javabean类代码 package google.test5;public class Student {private String name;private int age;public Student(){System.out.println("看看我打印了嘛&#xff1f;");}public Student(String name, int age){this.name name;this.age age;}public void …

Vue路由二(嵌套多级路由、路由query传参、路由命名、路由params传参、props配置、<router-link>的replace属性)

目录 1. 嵌套(多级)路由2. 路由query传参3. 路由命名4. 路由params传参5. props配置6. <router-link>的replace属性 1. 嵌套(多级)路由 pages/Car.vue <template><ul><li>car1</li><li>car2</li><li>car3</li></ul…

进程启动和进程终止

文章目录 进程程序进程进程ID进程表项C程序的启动过程启动例程进程终止atexit函数示例--终止函数的执行流程以及多种进程终止方式的对比 进程启动和退出流程图查看系统中的进程 进程 程序 程序是存放在磁盘文件中的可执行文件。当代码进行编辑保存后使用gcc等编译工具进行编译…

【机器学习】OpenCV入门与基础知识

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 OpenCV入门与基础知识简介安装与环境配置WindowsLinuxmacOS 核心数据结构MatSca…

Linux:进程状态和优先级

一、进程状态 1.1 操作系统学科&#xff08;运行、阻塞、挂起&#xff09; 为了弄明白正在运行的进程是什么意思&#xff0c;我们需要知道进程的不同状态 大多数操作系统都遵循以下原则 1.1.1 运行状态 因为有一个调度器需要确保CPU的资源被合理使用&#xff0c;所以需要维护…

【C++】学完c语言后的c++基础知识补充!(命名空间、输入和输出、缺省函数、函数重载、引用、内联函数代替宏、nullptr代替NULL)

一. 命名空间 1. 定义 出现的意义&#xff1a;解决各种函数、关键词和类的名称冲突问题。 定义方式&#xff1a;namespace 命名空间的名字 { } &#xff08;注意&#xff01;}后面不加&#xff1b;&#xff09; namespace 是关键词命名空间的…

CenterNet官方代码—目标检测模型推理部分解析与项目启动

CenterNet模型推理部分解析 CenterNet官方代码环境部署 CenterNet作为2019年CVPR推出的论文&#xff0c;论文中给出了官方代码所在的github仓库地址。https://github.com/xingyizhou/CenterNet。 整个代码的代码量并不是特别大&#xff0c;但整个项目的难点在于使用了老版本的…

横向移动-WMI

什么是WMI? WMI是基于 Web 的企业管理 (WBEM) 的 Windows 实现&#xff0c;WBEM 是跨设备访问管理信息的企业标准。 WBEM&#xff08;Web-Based Enterprise Management&#xff09;是一个开放标准&#xff0c;用于跨平台和跨设备的管理信息访问。WMI&#xff08;Windows Mana…

VMware Fusion虚拟机Mac版 安装Win10系统教程

Mac分享吧 文章目录 Win10安装完成&#xff0c;软件打开效果一、VMware安装Windows10虚拟机1️⃣&#xff1a;准备镜像2️⃣&#xff1a;创建虚拟机3️⃣&#xff1a;虚拟机设置4️⃣&#xff1a;安装虚拟机&#xff08;步骤和Win11安装步骤类似&#xff0c;此处相同步骤处没换…

C++从入门到起飞之——继承下篇(万字详解) 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1、派⽣类的默认成员函数 1.1 四个常⻅默认成员函数 1.2 实现⼀个不能被继承的类 ​编辑 2. 继承与友…

词嵌入(二):基于上下文窗口的静态词嵌入(从NNLM、CW模型谈到基于层次Softmax、负采样的Word2Vec模型)

文章目录 一、经典神经语言模型&#xff08;A Neural Probabilistic Language Model&#xff09;二、C&W模型 (Collobert and Weston, 2008)2.1 文章背景2.2 模型架构&#xff08;词向量的表示&#xff09;2.2.1 Lookup-Table Layer&#xff08;查找表&#xff09;2.2.2 TD…

STM32关于keil使用过程中遇到的问题

1.设备管理器STlink驱动确认安装完成&#xff0c;但是keil里一直识别不到&#xff0c;换下载器也没用 &#xff08;1&#xff09;问题描述 我的问题是这样产生的&#xff1a;之前用标准库开发STM32的时候&#xff0c;STLink能够正常使用&#xff0c;然后使用HAL库开发的时候出…

仓储管理系统的设计与实现SSM框架

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

electron react离线使用monaco-editor

目录 1.搭建一个 electron-vite 项目 2.安装monaco-editor/react和monaco-editor 3.引入并做monaco-editor离线配置 4.react中使用 5.完整代码示例 6.monaco-editor离线配置官方说明 7.测试 1.搭建一个 electron-vite 项目 pnpm create quick-start/electron 参考链接…

React学习day06-异步操作、ReactRouter的概念及简单使用

13、续 &#xff08;8&#xff09;异步状态操作 1&#xff09;在子仓库中 ①创建仓库 ②解构需要的方法 ③安装axios ④封装并导出请求 ⑤在reducer中为newsList赋值 ⑥获取并导出reducer函数 2&#xff09;在入口文件index.js中&#xff0c;注入 3&#xff09;在App.js中&a…

Vue.js入门系列(二十九):深入理解编程式路由导航、路由组件缓存与路由守卫

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

爬虫--翻页tips

免责声明&#xff1a;本文仅做分享&#xff01; 伪线程 from DrissionPage import ChromiumPage import timepage ChromiumPage() page.get("https://you.ctrip.com/sight/taian746.html") # 初始化 第0页 index_page 0# 翻页点击函数 sleep def page_turn():page…

【Linux修行路】网络套接字编程——UDP

目录 ⛳️推荐 前言 六、Udp Server 端代码 6.1 socket——创建套接字 6.2 bind——将套接字与一个 IP 和端口号进行绑定 6.3 recvfrom——从服务器的套接字里读取数据 6.4 sendto——向指定套接字中发送数据 6.5 绑定 ip 和端口号时的注意事项 6.5.1 云服务器禁止直接…

C++复习day12

IO流 一、C语言的输入和输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键 盘)读取数据&#xff0c;并将值存放在变量中。printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。 注意宽度输出和精度输出控制。C语言借助了…