【C++篇】C++的动态分配内存

news2024/12/27 22:23:01

友情链接:C/C++系列系统学习目录

知识点内容正确性以C++ Primer(中文版第五版)、C++ Primer Plus(中文版第六版)为标准,同时参考其它各类书籍、优质文章等,总结归纳出个人认为较有逻辑的整体框架,以至减少知识点上的错误,同时方便本人的基础复习,也希望能帮助到大家
 
最好的好人,都是犯过错误的过来人;一个人往往因为有一点小小的缺点,将来会变得更好。如有错漏之处,敬请指正,有更好的方法,也希望不吝提出。最好的生活方式就是和努力的大家,一起奔跑在路上


文章目录

  • 🚀一、内存分区
  • 🚀二、new和delete
    • ⛳(一)普通new运算符
    • ⛳(二)定位new运算符
  • 🚀三、类和动态内存分配
    • ⛳(一)对类成员使用动态内存分配
    • ⛳(二)使用new创建对象
  • 🚀四、继承和动态内存分配
    • ⛳(一)基类使用new,派生类不使用new
    • ⛳(二)派生类使用new


🚀一、内存分区

内存分区:

在C++中,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区;

在C中,C内存区分为堆、栈、全局/静态存储区、常量存储区;

malloc是从堆上开辟空间,而new是从自由存储区开辟;(自由存储区是 C++抽象出来的概念,不仅可以是堆,还可以是静态存储区)。

1. C内存分布

  • BSS段: 用来存放程序中未初始化的全局变量。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
  • 数据段:用来存放程序中已初始化的全局变量。数据段属于静态内存分配。
  • 代码段:用来存放程序执行代码。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等(相当于文字常量区??)。
  • 堆:堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc/free等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张)/释放的内存从堆中被剔除(堆被缩减)
  • 栈:栈又称堆栈, 存放程序的局部变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外在函数被调用时,栈用来传递参数和返回值。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。

2.C++内存分布

  • 栈:内存由编译器在需要时自动分配和释放。通常用来存储局部变量和函数参数。
  • 堆:内存使用new进行分配使用delete或delete[]释放。如果未能对内存进行正确的释放,会造成内存泄漏。但在程序结束时,会由操作系统自动回收。
  • 自由存储区:使用malloc进行分配,使用free进行回收。和堆类似。
  • 全局/静态存储区:全局变量和静态变量被分配到同一块内存中,C语言中区分初始化和未初始化的,C++中不再区分了。
  • 常量存储区:存储常量,不允许被修改。

3.区分栈和堆

  • 管理方式:栈由编译器管理,堆由程序员控制。

  • 空间大小:VC下栈默认是1MB,堆在32位的系统上可以达到4GB。

  • 碎片问题:栈不会产生碎片,堆会产生碎片。

  • 生长方向:堆向着内存地址增加的方向增长,栈向着内存地址减少的方向增长。

  • 分配方式:堆是动态分配的。栈是静态分配和动态分配的,静态分配由编译器完成,动态分配由alloca函数进行分配,由编译器释放。

  • 分配效率:栈的分配效率非常高。堆的分配机制很复杂,效率比栈要低得多。

4.c语言存储类别,c++存储方案

C语言有4种存储类别:自动的(auto)、静态的(static)、寄存器(register)、外部的(exteren)

C++(自C++11起)使用四种不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间。

  • 自动存储: 在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。:

  • 静态存储: 在函数定义外被定义的变量和使用关键字 static 定义的变量。它们在程序整个运行过程中都存在。C++有3种存储持续性为静态的变量。

    C++为静态存储持续性变量(静态变量)提供了3种链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。这3种链接性都在整个程序执行期间存在,与自动变量相比,它们的寿命更长。如果没有显示的初始化变量,默认情况下,静态数组、结构的每个元素或成员都被设置为0.

    int  global = 100; //static duration,外部链接性
    static int in_file = 10;  //static duration, 内部链接性
    int main(){
        static int count = 1; // static duration,无链接性
        ……
    }
    
  • 动态存储: 用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的持续性为动态,有时被称为自由存储(free store) 或 堆(heap)。

  • 线程存储(C++11):当前,多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。

🚀二、new和delete

⛳(一)普通new运算符

面向对象编程与传统的过程性编程的区别在于,OOP强调的是在运行阶段(而不是编译阶段)进行决策。运行阶段指的是程序正在运行时,编译阶段指的是编译器将程序组合起来时。运行阶段决策就好比度假时,选择参观哪些景点取决于天气和当时的心情;而编译阶段决策更像不管在什么条件下,都坚持预先设定的日程安排。

在运行阶段做决策并非OOP独有的,但使用C++编写这样的代码比使用C语言简单

C语言使用malloc()和free(),而C++使用new和delete,在C语言中,可以用库函数malloc( )来分配内存;在C++中仍然可以这样做,但C++还有更好的方法—new和delete运算符。

int *pn = new int;
delete pn;

//数组方式:
short * psome = new short[500];
delete [] ps;
  • new和delete是运算符,不是函数,因此执行效率更高
  • new 初始化对象,调用对象的构造函数,对应的delete调用相应的析构函数 ;malloc 仅仅分配内存,free仅仅回收内存。
  • 返回值问题,malloc开辟成功返回void*,需要进行强制类型转换,失败返回NULL;new成功返回对象指针,失败抛出异常。
  • psome是指向一个short(数组第一个元素)的指针,不能使用sizeof运算符来确定动态分配的数组包含的字节数。

计算机可能会由于没有足够的内存而无法满足new的请求。在这种情况下,new通常会引发异常—一种错误处理技术;而在较老的实现中,new将返回0。C++提供了检测并处理内存分配失败的工具。

new运算符初始化;

int *pi = new int (6); // *pi set to 6
double * pd = new double (99.99); // *pd set to 99.9 9

然而,要初始化常规结构或数组,需要使用大括号的列表初始化,这要求编译器支持C++11。C++11允许您这样做

struct where {double x; double y; double z;};
where * one = new where {2.5,5.3,7.2};  // C++11
int * ar = new int [4]{2,4,6,7};        //C++11

在C++11中,还可将列表初始化用于单值变量:

int *pin = new int {}; // *pi set to 6
double * pdo = new double {99.99} ;// *pd set to 99.99

创建动态数组:

C方式:

int *array;  //先定义一个指针变量

array=(int *)malloc(sizeof(int)); //然后用Malloc函数开辟一个内存空间。

int index;  //但是,这只是一个的内存,我们需要乘一个大小,所以定义一个变量。

scanf("%d",&index): //此时我们输入这个变量index的值。

array=(int *)malloc(sizeof(int)*index); //然后用Malloc函数开辟一个内存空间。然后我们将SIzeof乘上这个数就可以了。

C++方式:

对于管理一个小型数据对象来说,声明一个简单变量,这样做比使用new和指针更简单,尽管给人留下的印象不那么深刻。通常,对于大型数据(如数组、字符串和结构),应使用new,这正是new的用武之地。

int main()
{
    int n; 
    cin >> n;
    int* a = new int[n];
}

⛳(二)定位new运算符

在申请自由空间内存的时候,如果空间被耗尽,new就会失败,普通的new空间失败会throw bad_alloc异常,但使用定位new new(nothrow)之后,不会抛出异常,而是在申请空间失败的时候返回一个空指针。

包含new头文件

new运算符允许向new中传递额外的参数,这种形式成为定位new,参数在标准库中定义。bad_alloc 和 nothrow 都定义在 new头文件中

//参数不局限于nothrow
int *p_int = new(nothrow) int;
int *q_int = new(nothrow) int();

double* pd1 = new double[4];//使用堆内存
double* pd2 = new (buffer)double[4];//使用buffer内存

🚀三、类和动态内存分配

⛳(一)对类成员使用动态内存分配

在类的数据成员中使用指针时,对应的构造函数必须提供内存来存储数据

strngbad.h

// strngbad.h -- flawed string class definition
#include <iostream>
#ifndef STRNGBAD_H_
#define STRNGBAD_H_

class stringBad
{
private :
    char *str; // pointer to string
    int len;  //length of string
    static int num_strings ; // number of objects
public:
	stringBad (const char * s); //constructor
	stringBad () ; // default constructor
	~stringBad () ; // destructor
	//friend funetion
	friend std::ostream& operator<<(std::ostream & os,const StringBad & st) ;
#endif

strngbad.cpp

int stringBad::num_strings = 0;

stringBad::StringBad (const char *s)
{
    len = std : : strlen (s) ; //set size
    str = new char [len + 1] ; //allot storage
    std::strcpy(Str,s);        // initialize pointer
    num_strings++;             // set object count
    cout << rum_strings << " :\"" << str<< " \" object created\n" ; // For Your Information
}

stringBad::StringBad () //default constructor
{
    len = 4 ;
    str = new char [ 4 ] ;
    std::strapy(str,"C++"); // default string
    num_strings++;
    cout << num_strings << " :\"" <c str
         <<" \ " default object created[n"; //FYI
}

stringBad:: ~stringBad ()  //necessary destructor
{
    cout << " \"" << str << " \" object deleted, "; //FYI
    --num_strings ;                                 // required
    cout << num_strings<< " left\n" ;               // FYI
    delete [] str;                                  // required
}

std::ostream& operator<<(std::ostream & os,const StringBad & st)
    os << st.str;
    return os;
}
  • 在构造函数中不能这样做:

    str = s;
    

    这只保存了地址,而没有创建字符串副本。

  • 在默认构造函数或构造函数都使用了动态内存分配,析构函数中必须要使用delete回收内存,str成员指向new分配的内存。当StringBad对象过期时,str指针也将过期。但str指向的内存仍被分配,除非使用delete将其释放。删除对象可以释放对象本身占用的内存,但并不能自动释放属于对象成员的指针指向的内存。因此,必须使用析构函数。在析构函数中使用delete语句可确保对象过期时,由构造函数使用new分配的内存被释放。

  • 如果有多个构造函数,则必须以相同的方式使用new,要么都带中括号,要么都不带。因为只有一个析构函数,所有的构造函数都必须与它兼容。然而,可以在一个构造函数中使用new初始化指针,而在另一个构造函数中将指针初始化为空(0或C++11中的nullptr),这是因为delete(无论是带中括号还是不带中括号)可以用于空指针。

⛳(二)使用new创建对象

Class name * pclass = new Class_name ("陈七");
  1. 根据参数类型选择调用哪种构造函数,如此例将调用如下构造函数:Class name (char *) ;下面的初始化方式将调用默认构造函数:
Class_rame *ptr = new Class_rame;

同时delete该对象时会调用析构函数

注意区分new对象时加括号()与不加括号():

#include <iostream>
using namespace std;

class MyClass
{
public:
	int getValue()
	{
		return _value;
	}

private:
	int _value;
};


int main(int argc, char **argv)
{
	MyClass *a = new MyClass;
	MyClass *b = new MyClass();

	cout << "a = " << a->getValue() << endl;    //对象b的数据成员被初始化为0,而对象a是随机值
	cout << "b = " << b->getValue() << endl;

	return 0;
}
  • 当我们提供了构造函数和析构函数时,此时不管带不带括号,在new一个对象时只进行内存分配,初始化和赋值工作依赖于我们提供的构造函数。

  • 如果未提供,如上:

    MyClass *a = new MyClass; 在进行内存分配的同时不会对内存进行默认初始化(随机值).

    MyClass *b = new MyClass(); 在进行内存分配的同时并对内存进行默认初始化(0).

结论:都是调用默认构造函数,但尽量使用 MyClass *b = new MyClass();的形式,并为该类编写构造函数并进行一系列的初始化或赋值操作,不要依赖系统默认未定义行为。

  1. 注意区分为整个对象分配内存与为类的某个指针成员分配内存,后者是在构造函数中实现的,同时记得在析构函数中delete该内存

  2. 静态数据成员独立于对象被保存,所以为整个对象分配内存时,只为指针和其它成员分配内存(注意是为指针变量分配内存,指针指向的内存由构造函数分配),创建对象将调用构造函数,后者分配用于指针的内存。然后,当程序不再需要该对象时,使用delete删除它。这将只释放用于指针和len成员的空间,并不释放指针指向的内存,而该任务将由析构函数来完成

在这里插入图片描述

🚀四、继承和动态内存分配

如果基类使用动态内存分配,并重新定义赋值和复制构造函数,这将怎样影响派生类的实现呢?这个问题的答案取决于派生类的属性。如果派生类也使用动态内存分配,那么就需要学习几个新的小技巧。下面来看看这两种情况。

⛳(一)基类使用new,派生类不使用new

#include<iostream>
#include<string.h>
using namespace std;
 
class baseDMA
{
private:
    char *label;
    int rating;
public:
    baseDMA(const char *l = "null", int r=0);  //默认构造函数
    baseDMA(const baseDMA &rs);                //拷贝构造函数
    virtual ~baseDMA();                        //析构函数
    baseDMA &operator=(const baseDMA &rs);     //重载赋值运算符
};
 
class lacksDMA:public baseDMA
{
private:
    char color[40];
public:
 	...
};
  1. 上面代码只给出声明不给出实现,关于动态内存分配在构造函数与析构函数中

  2. 不需要为lackDMA类定义显式析构函数、复制构造函数和赋值运算符:

    • 析构函数:对于我们的子类lacksDMA,因为其并没有开辟动态内存空间,因此其不需要析构函数(使用默认析构函数即可) 。从父类继承的指针成员会先调用子类默认析构函数(什么都没做),再调用父类的析构函数delete掉

    • 拷贝构造函数:在拷贝构造函数中使用初始化列表方式(显示方式)调用baseDMA复制构造函数来复制lacksDMA对象的baseDMA部分。因此,默认复制构造函数对于新的lacksDMA成员来说是合适的,同时对于继承的baseDMA对象来说也是合适的。

    • 对于赋值来说也是一样,类的默认赋值运算符将自动使用基类的赋值运算符来对基类组件进行赋值!

⛳(二)派生类使用new

class hasDMA:public baseDMA
{
private:
    char * style; //在构造函数中使用new
public:
 	...
};

在这种情况下,必须为派生类定义显式析构函数、复制构造函数和赋值运算符:

  • 析构函数:

    baseDMA::~baseDMA() // takes care of baseDMA stuff
    {
    	delete [] label ;
    }
    
    hasDMA::~haSDMA() // takes care of hasDMA stuff
    {
    	delete [] style;
    }
    
    
  • 复制构造函数:

    baseDMA::baseDMA(const baseDMA & rs)
    {
        label = new char [std::strlen(rs.label) + 1];
        std::strcpy (label,rs.label);
        rating = rs.rating;
    )
        
    hasDMA::hasDMA (const hasDMA& hs):baseDMA (hs)
    {
    	style = new char [std::strlen(hs.style) + l];
        std::strcpy (style, hs.style);
    )
    
  • 赋值运算符:

    baseDMA & baseDMA::operator=(const baseDMA & rs)
    {
        if( this == &rs)
        	return *this;
        delete [] label ;
        label = new char [std::strlen(rs.label) + 1];
        std::strcpy (label, rs.label);
        rating = rs.rating;
        return *this;
    )
    
    hasDMA & hasDMA::operator= (const hasDMA & hs)
    {
        if(this == &hs)
        	return *this;
        baseDMA::operator= (hs); //copy base portion
        delete [] style;        //prepare for new style
        style = new char [std::strlen (hs.style) + 1];
        std::strcpy(style,hs.style);
        return *this;
    }
    

行文至此,落笔为终。文末搁笔,思绪驳杂。只道谢不道别。早晚复相逢,且祝诸君平安喜乐,万事顺意。

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

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

相关文章

处理 Python 3.11 弃用的 PySys_SetPath 和 Py_SetProgramName

在C调用matplotlibcpp.h画图时报错&#xff0c;使用的python版本是3.11.3版本。 解决方案&#xff1a;不重要的话&#xff0c;注释该行代码即可。 Python 3.11 弃用 PySys_SetPath 和 Py_SetProgramName。这 PyConfig API 取代了这些功能和其他功能。此提交使用 PyConfig API …

深入理解深度学习——GPT(Generative Pre-Trained Transformer):基础知识

分类目录&#xff1a;《深入理解深度学习》总目录 《深入理解深度学习——Transformer》系列文章介绍了Transformer&#xff0c;该模型最初被用于机器翻译任务&#xff0c;其出色表现引起了学术界的极大兴趣&#xff0c;其优异的特征提取与语义抽象能力得到了学者的广泛认可&am…

企业做seo有什么好处?SEO 为您的企业带来的 10 大主要优势?

如果您希望建立长期的品牌知名度、扩大目标受众并赚取更多收入&#xff0c;那么搜索引擎优化至关重要。让我们看看 SEO 为您的企业带来的 10 大好处&#xff0c;以及如何实现它们。 1. 它提高了你的可信度 在搜索引擎结果页面上排名靠前的网站通常被搜索引擎认为是高质量和值得…

【unity细节】—怎么将unity编译时和运行时的功能隔开

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity细节和bug ⭐怎么将unity编译时和运行时的功能隔开的问题⭐ 文章目录 ⭐怎么将unity编译时和…

TCP的十个核心机制

目录 前言一 到 三四. 滑动窗口五. 流量控制六. 拥塞控制七. 延时应答八. 捎带应答九. 面向字节流十. 异常处理总结 前言 TCP协议是传输层的重点协议, 负责将数据从发送端传输到接收端. TCP协议是传输控制协议, 顾名思义也就是对数据的传输进行控制的协议. TCP 协议有很多, 我…

android存储3--初始化.unlock事件的处理

android版本&#xff1a;android-11.0.0_r21http://aospxref.com/android-11.0.0_r21 概述&#xff1a;收到unlock事件后&#xff0c;StorageSessionController、vold、storaged模块进行各自初始化操作。 一、StorageManagerService::onUnlockUser处理unlock事件 设备解锁后…

openlayers瓦片的使用

OpenLayers是一个用于WebGIS客户端的地图开发库&#xff0c;支持多种地图。在使用瓦片时&#xff0c;先将自己需要的瓦片下载&#xff0c;下载好的瓦片会分层&#xff0c;越高的层级瓦片的数量余额多。 使用时可以引入 ol.js 文件&#xff0c;和 ol.css 文件&#xff0c;或者使…

机器学习 | 实验五:LDA

LDA的思想&#xff1a;“投影后类内方差最小&#xff0c;类间方差最大”。即数据在低维度上进行投影&#xff0c;投影后希望每一种类别数据的投影点尽可能的接近&#xff0c;而不同类别的数据的类别中心之间的距离尽可能的大。 假设我们有两类数据分别为红色和蓝色&#xff0c;…

关于socket编程中FD_XXX以及select函数的理解

文章目录 01 | 宏接口定义02 | 使用方法03 | 服务端代码示例 学习socket编程的时候看到很多FD开头的宏定义和函数&#xff0c;这里记录一下这些宏定义和函数的含义及处理流程 01 | 宏接口定义 fd_set fd_set 是一种表示文件描述符的集合类型&#xff0c;在socket编程中&#xf…

计算机网络——自顶向下方法(第三章学习记录)

本章学习运输层 运输层位于应用层和网络层之间&#xff0c;是分层的网络体系的重要部分&#xff0c;该层为运行在不同主机上的应用进程提供直接的通信服务起着至关重要的作用。 运输层协议为运行在不同主机上的应用进程之间提供了逻辑通信(logic communication)功能。从应用程…

CSS3-补充-伪元素

伪元素 作用&#xff1a;在网页中创建非主体内容&#xff0c;开发中常用CSS创建标签&#xff0c;比如装饰性的不重要的小图 区别&#xff1a; 1 元素&#xff1a;HTML 设置的标签 2 伪元素&#xff1a;由 CSS 模拟出的标签效果 …

EMC学习笔记(七)阻抗控制(一)

阻抗控制&#xff08;一&#xff09; 1.特征阻抗的物理意义1.1 输入阻抗1.2 特征阻抗1.3 偶模阻抗、奇模阻抗、差分阻抗 2.生产工艺对阻抗控制的影响 1.特征阻抗的物理意义 1.1 输入阻抗 在集总电路中&#xff0c;输入阻抗是经常使用的一个术语 &#xff0c;它的物理意义是: …

FreeRTOS实时操作系统(六)列表与列表项

系列文章目录 文章目录 系列文章目录简要概念列表列表项迷你列表项 相关API函数初始化列表列表项初始化列表项插入&#xff08;升序&#xff09;末尾列表项插入列表项删除 实战实验 简要概念 列表是 FreeRTOS 中的一个数据结构&#xff0c;概念上和链表有点类似&#xff0c;列…

ubuntu环境下测试硬盘读写速度

在Ubuntu下&#xff0c;可以使用hdparm、dd和fio等工具来测试硬盘的读写速度。 开始之前&#xff0c;先使用sudo fdisk -l命令来列出系统中所有的硬盘和分区&#xff1a; 1.使用hdparm测试硬盘读取速度&#xff1a; 安装hdparm&#xff1a; sudo apt-get install hdparm 通…

C++17中utf-8 character literal的使用

一个形如42的值被称作字面值常量(literal),这样的值一望而知。每个字面值常量都对应一种数据类型&#xff0c;字面值常量的形式和值决定了它的数据类型。 由单引号括起来的一个字符称为char型字面值&#xff0c;双引号括起来的零个或多个字符则构成字符串型字面值。 字符串字面…

9.QT 三目运算符

上面引出两个新的概念&#xff1a; 左值&#xff1a;能被赋值的就是左值。 右值&#xff1a;不能被赋值的就是右值。

基於ranger,kerberos,hadoop ha 配置hvie多用戶

基於ranger&#xff0c;kerberos&#xff0c;hadoop ha 配置hvie多用戶 hive多用戶權限管理一、hive的管理員用戶二、hive配置普通用戶1.添加用戶2.配置kerberos2.1 创建主体2.2 生成keytab文件2.3 修改keytab文件所有者(可做可不做) 3. 配置windows hive多用戶權限管理 一、h…

English Learning - L3 综合练习 8 TED-Living Beyond the Limits 2023.06.21 周三

English Learning - L3 综合练习 8 TED-Living Beyond the Limits 2023.06.21 周三 句 1句 2扩展 句 3句 4句 5句 6句 7扩展 random 句 8扩展 句 9句 10句 11句 12句 13句 14句 15句 16句 17句 18句 19句 20句 21句 22句 23 句 1 Four months later I was back up on a snowbo…

SVN使用步骤

1.基本操作 2.提交之间看一下变更内容 3.显示日志 是查看所有提交的记录4.撤销和恢复操作 撤销本地修改 或者点击提交的时候 还原 把修改的撤销掉 第二种情况,内容已经提交上去了点击提交日志 进行操作 只是撤销了本地 接着还需要继续提交到服务端 第三种情况 我们需要恢…

Linux系统之安装showdoc文档工具

Linux系统之部署showdoc文档工具 一、showdoc介绍1.1 showdoc简介1.2 showdoc功能 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本3.3 检查本地yum仓库状态 四、安装httpd服务4.1 安装httpd4.2 启动httpd服务…