C / C++ 内存管理

目录

1、C / C++内存分布

2、C语言中动态内存管理方式

        malloc / calloc / realloc / free

3、C++内存管理方式

        new / delete 操作内置类型

        new / delete 操作自定义类型

4、operator new与operator delete函数(重要点进行讲解)

        operator new与operator delete函数(重点)

        operator new与operator delete的类专属重载(了解)

5、new和delete的实现原理

        内置类型

        自定义类型

        拓展:delete要匹配使用

6、定位new表达式(placement-new) (了解)

7、常见面试题

        7.1、malloc/free和new/delete的区别

        7.2、内存泄漏

                什么是内存泄漏,内存泄漏的危害

                内存泄漏分类(了解)

                如何检测内存泄漏(了解)

                如何避免内存泄漏

        7.3、如何一次在堆上申请4G的内存?


1、C / C++内存分布

我们先来看下面的一段代码和相关问题

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
	static int staticVar = 1;
	int localVar = 1;
	int num1[10] = { 1, 2, 3, 4 };
	char char2[] = "abcd";
	const char* pChar3 = "abcd";
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
	free(ptr1);
	free(ptr3);
}

来看看如下的几个问题:

1. 选择题:
选项: A.栈 B.堆 C.数据段 D.代码段
globalVar在哪里?__C__ staticGlobalVar在哪里?__C__
staticVar在哪里?__C__ localVar在哪里?__A__
num1 在哪里?__A__
char2在哪里?__A__ *char2在哪里?__A__
pChar3在哪里?__A__ *pChar3在哪里?__D__
ptr1在哪里?__A__ *ptr1在哪里?__B__

2. 填空题:
sizeof(num1) = __40__;
sizeof(char2) = __5__; strlen(char2) = __4__;
sizeof(pChar3) = __4/8__; strlen(pChar3) = __4__;
sizeof(ptr1) = __4/8__;

其实这部分内容在C语言的时候我已经讲解过,这里给出博客链接C/C++内存分配

这里给出一幅图:

C/C++程序内存分配的几个区域:

  • 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  • 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
  • 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  • 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

想看更多这方面练习的小伙伴可以点击这个链接:sizeof 、strlen 内存分配练习


2、C语言中动态内存管理方式

malloc / calloc / realloc / free

这部分内容我在C语言的博客中有详细全面的讲解,可以点击这块链接查看:C语言动态内存管理

这边给出代码演示:

void Test()
{
    int* p1 = (int*)malloc(sizeof(int));
    free(p1);
    int* p2 = (int*)calloc(4, sizeof(int));
    // 尝试重新分配内存
    int* p3 = (int*)realloc(p2, sizeof(int) * 10);
    if (p3 != NULL) {
        // realloc 成功,p2 指向的旧内存已被释放,只需释放 p3
        free(p3);
    }
    else {
        // realloc 失败,p2 指向的内存仍然有效,需要释放 p2
        free(p2);
    }
}
  • malloc:

在内存的动态存储区中分配一块长度为size字节的连续区域,参数size为需要内存空间的长度,返回该区域的首地址

  • calloc:

与malloc相似,不过函数calloc() 会将所分配的内存空间中的每一位都初始化为零

  • realloc:

 给一个已经分配了地址的指针重新分配空间,可以做到对动态开辟内存大小的调整。

  • 【面试题】:malloc/calloc/realloc的区别?
  1. 函数malloc不能初始化所分配的内存空间,而函数calloc能.如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之, 如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据.也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题.
  2. 函数calloc() 会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那么这些元素通常会被初始化为空指针;
  3. 函数malloc向系统申请分配指定size个字节的内存空间.返回类型是 void类型.void表示未确定类型的指针.C,C++规定,void* 类型可以强制转换为任何其它类型的指针.
  4. realloc可以对给定的指针所指的空间进行扩大或者缩小,无论是扩张或是缩小,原有内存的中内容将保持不变.当然,对于缩小,则被缩小的那一部分的内容会丢失.realloc并不保证调整后的内存空间和原来的内存空间保持同一内存地址.相反,realloc返回的指针很可能指向一个新的地址.
  5. realloc是从堆上分配内存的.当扩大一块内存空间时,realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,此时即原地扩;如果数据后面的字节不够,那么就使用堆上第一个有足够大小的自由块,现存的数据然后就被拷贝至新的位置,而老块则放回到堆上.这句话传递的一个重要的信息就是数据可能被移动,即异地扩

3、C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过newdelete操作符进行动态内存管理

new / delete 操作内置类型

void Test()
{
	// new一个int类型的空间
	int* ptr4 = new int;
	// new一个int类型的空间并初始化为10
	int* ptr5 = new int(10);
	// new10个int类型的空间
	int* ptr6 = new int[10];
	// new10个int类型的空间并初始化
	int* ptr7 = new int[10]{ 10,9,8,7,6,5 }; //跟数组的初始化很像,大括号有几个,初始化几个,其余为0。不过C++11才支持的语法
	delete ptr4;
	delete ptr5;
	delete[] ptr6;
	delete[] ptr7;
}

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[ ]和delete[ ]

总结:对于内置类型而言,用malloc和new,除了用法不同,没有什么区别。它们的区别在于自定义类型

new / delete 操作自定义类型

先给出结论:

  • 申请空间时:malloc只开空间,new既开空间又调用构造函数初始化。
  • 释放空间时:delete会调用析构函数,free不会

先看下malloc和free:

很明显,malloc的对象只是开辟了空间,并没有初始化,free后也只是普通的释放。

再看下new和delete:

当我们运行程序时,结果如下:

很明显,使用new,既可以开辟空间,又调用了构造函数从而完成初始化,而delete时调用了析构函数,以此释放空间。

在我们先前学习的链表中,C语言为了创建一个节点并将其初始化,需要单独封装一个函数进行初始化,我C++只需要用new即可开空间+初始化:

struct ListNode
{
	struct ListNode* _next;
	int _val;
    //构造函数
	ListNode(int val = 0) 
		:_next(nullptr)
		,_val(val)
	{}
};
int main()
{
	ListNode* n2 = new ListNode(10); //C++的new相当于我之前的BuyListNode函数
	return 0;
}

如若只是单纯的区分malloc和new,那么malloc纯粹只开空间不初始化,而new既开空间又初始化。

注1:new会自动调用构造函数,前提得是默认构造函数,如果不是默认构造函数,我们可以显示初始化,如下演示:

class A
{
public:
	A(int a)
		:_a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = new A(1);
	A a1(1), a2(2), a3(3);
	A* p2 = new A[3]{ a1, a2, a3 };//有名对象
	A* p3 = new A[3]{ 1, 2, 3 };//隐式匿名对象的类型转换
    delete p1;
    delete[] p2;
    delete[] p3;
	return 0;
}
  • 注意我在开辟数组空间p2和p3时,由于没有默认构造函数,因此需要显示传值初始化,我既可以用有名对象的形式,也可以用匿名对象的隐式类型转换的形式,这里刚好回应了我们前面所学习到的知识

注2:当我类里的构造函数是多参数的形式呢(同样没有默认构造函数),申请空间的形式如下:

class A
{
public:
	A(int a1, int a2)
		:_a1(a1)
		,_a2(a2)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a1;
	int _a2;
};
int main()
{
	A* p1 = new A(1, 1);
	A a1(1, 1);
	A a2 = { 2, 2 }, a3 = { 3, 3 };
	A* p2 = new A[3]{ a1, a2, a3 };//有名对象
	//多参数的构造函数也支持隐式类型转换
	A* p3 = new A[3]{ {1, 1}, {2, 2}, {3, 3} };//匿名对象的类型转换
	//A* p4 = new A[5]{ {1, 1}, {2, 2}, {3, 3} }; err错误

    delete p1;
    delete[] p2;
    delete[] p3;
	return 0;
}

我上面的p4申请空间是有问题的,因为我没有默认构造函数,而我p4申请了5个A类型的空间,前3个通过类型转换显示传参调用构造函数没有问题,可还剩下的2个会调用默认构造函数,但又没有默认构造函数,所以会有问题,当我给构造函数加上缺省值时,p4就不会有问题了:

总结:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会;如果没有默认构造函数,就需要显示传参,如果有就不用

  • new和malloc还有一个区别就是在申请内存失败时的处理情况不同。

malloc如若开辟内存失败,会返回空指针这个我们都晓得的,但是new失败抛异常

仔细观察下面这段代码:

int main()
{
    //malloc失败,返回空指针
	int* p1 = (int*)malloc(sizeof(int) * 10);
	assert(p1); //malloc出来的p1需要检查合法性
    //new失败,抛异常
	int* p2 = new int;
	//new出来的p2不需要检查合法性
}

为了演示malloc和new在开辟内存时失败的场景,这里给出一份测试:

int main()
{
	void* p3 = malloc(1024 * 1024 * 1024); //1G
	cout << p3 << endl;
	void* p4 = new char[1024 * 1024 * 1024];
	cout << p4 << endl;
}

换个顺序看看:

此段测试充分说明了我先开辟1G的大小是没有问题的,但是再开辟1个G的大小就会报错了,为了能够看出malloc和new均报错的场景,我们再定义一个指针占据这1G:

此段测试更能够清楚的看出mallloc失败会返回空指针,而new失败会抛异常。 对于抛异常,我们理应进行捕获,不过这块内容我后续会讲到,这里先给个演示:


4、operator new与operator delete函数(重要点进行讲解)

operator new与operator delete函数(重点)

newdelete是用户进行动态内存申请和释放的操作符operator newoperator delete是系统提供的全局函数new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

  • 注意:operator new和operator delete不是对new和delete的重载,这是俩库函数。

源码链接:operator new、operator delete

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK); /* block other threads */
	__TRY
		/* get a pointer to memory block header */
		pHead = pHdr(pUserData);
	/* verify block type */
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK); /* release other threads */
	__END_TRY_FINALLY
		return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
//通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间
//成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。
//operator delete 最终是通过free来释放空间的。

operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。operator new本质是封装了malloc。operator delete本质是封装了free。

  • 具体使用operator new和operator delete的操作如下:
int main()
{
	Stack* ps2 = (Stack*)operator new(sizeof(Stack));
	operator delete(ps2);

	Stack* ps1 = (Stack*)malloc(sizeof(Stack));
    assert(ps1);
	free(ps1);
}

operator new和operator delete的功能和malloc、free一样。也不会去调用构造函数和析构函数,不过还是有区别的,1、operator new不需要检查开辟空间的合法性。2、operator new开辟空间失败就抛异常

  • operator new和operator delete的意义体现在new和delete的底层原理
Stack* ps3 = new Stack;
new的底层原理:转换成调用operator new + 构造函数
delete ps3;
delete的底层原理:转换成调用operator delete + 析构函数

new的底层原理就是转换成调用operator new + 构造函数,我们可以通过查看反汇编来验证:

delete也是转换成调用operator delete + 析构函数,不过是先调用的析构,再调用的operator delete:

这里画图演示总结:

operator new与operator delete的类专属重载(了解)

为了避免有些情况下我们反复的向堆申请释放空间,于是产生池化技术(内存池),直接找内存池申请释放空间,此时效率更高更快。以后会详细讲解到池化技术,这里简要了解。而上述这俩的类专属重载就是在new调用operator new的时候就可以走内存池的机制从而提高效率。


5、new和delete的实现原理

内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间而且new在申请空间失败时会抛异常,malloc会返回NULL

自定义类型

new的原理

  1. 调用operator new函数申请空间
  2. 在申请的空间上执行构造函数,完成对象的构造

delete的原理

  1. 在空间上执行析构函数,完成对象中资源的清理工作
  2. 调用operator delete函数释放对象的空间

new T[N]的原理

  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
  2. 在申请的空间上执行N次构造函数

delete[ ]的原理

  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

拓展:delete要匹配使用

注意:delete要匹配使用,否则有时候会报错,有时候不会报错。对于如下的混搭方式,编译器不会报错,也没有内存泄漏的风险:

int main()
{
	int* p1 = (int*)malloc(sizeof(int) * 10);
	delete p1;
	int* p2 = new int[10];
	delete p2;
	return 0;
}

上述没出问题是建立在内置类型的基础上,对于内置类型,不存在构造函数的说法,调用new正常的底层调用operator new,再调用malloc,调用delete就直接转换成调用operator delete,再调用free,没有问题。如果是在自定义类型的基础上就会有问题了:

class A
{
public:
	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a1;
	int _a2;
};
int main()
{
	int* p1 = new int[10];
	delete p1;
	A* p2 = new A[10];
	delete p2;
	return 0;
}

对于开辟的内置类型p1,这里开辟了40字节空间的大小,我们可以通过反汇编来验证:

对于自定义类型的p2,单看代码,应该开辟了 (sizeof(A)=8) * 10 = 80 个字节,但实际却开了84,通过反汇编得到:

对于p2,本来就开辟80字节,实际开辟84字节,多出的4字节用于存个数10放在最前面,我们可以通过内存窗口看出:

当申请空间后,返回的起始位置并不是从最前面开始,而是从多出的4字节后面开始

这里最前面存的10个值是给delete[ ]用的,delete[ ]不知道要调用多少次析构函数,但delete[ ]会在调用时减去多的4个字节到最前面,并且调用operator delete时不能直接释放图示p2的位置,要从p2再往前4字节的位置开始释放。申请一段空间后,不能从中间开始释放,否则就会报错,内置类型就不会存在这个问题,因为它不会多开这4个字节,也不会存在调用构造和析构函数的问题。

int main()
{
	int* p1 = new int[10];
	delete p1;
	A* p2 = new A[10];
	delete p2;
	return 0;
}

综上:上述直接用delete p2存在两个问题:

  1. 析构函数没有调完,导致内存泄漏
  2. 弹框报错的原因在于你释放的位置不对,直接用delete p2相当于直接从p2的位置开始释放,但是会漏掉前面多开的4字节,而调用delete[ ] p2就会自动从p2开始往前4个字节的位置开始释放,正确

改成delete[] p2就不会有问题了:

注:当我把上述自定义类型A中的析构函数省去时,delete p2竟然不会报错了:

在析构函数注释取消之前,p2申请了84字节的空间,加上注释后竟然又回到了80字节:

这里其实编译器进行了优化,编译器看到你没有显示实现析构函数,而编译器自己生成的析构函数也没有释放什么资源,因此就认为可调可不调,干脆优化成不调用了,此时我new出来的对象就不会多开那4个字节,此时delete p2释放的位置也就对了,也就没有报错了,同时也没有内存泄漏。但并不是说不报错就是对的,还是要按照规则办事,因为不同的编译器处理情况是不一样的。综上:不要去乱匹配,按规矩办事!


6、定位new表达式(placement-new) (了解)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象

使用格式:

new (place_address) type或者new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景:

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化

class Test
{
public:
	Test(int date = 2)
		: _data(date)
	{
		cout << "Test():" << this << endl;
	}
	~Test()
	{
		cout << "~Test():" << this << endl;
	}
private:
	int _data;
};
int main()
{
	// pt现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	Test* pt1 = (Test*)operator new(sizeof(Test));
    //new (place_address) type
	new(pt1)Test; // 注意:如果Test类的构造函数有参数时,此处需要传参
    //释放空间delete的过程:析构+operator = new
    pt1->~Test();
    operator delete(pt1);

    //new(place_address) type(initializer - list)
	Test* pt2 = (Test*)operator new(sizeof(Test));
	new(pt2)Test(10);
    //释放空间delete的过程:析构+operator = new
    pt2->~Test();
    operator delete(pt2);

    //对于pt1的操作,等价于如下,pt2同理:
 /* Test* pt1 = new Test;
    delete pt1; */
}

7、常见面试题

7.1、malloc/free和new/delete的区别

共同点:

  • 都是从堆上申请空间,并且需要用户手动释放。

不同点:

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常(底层区别)
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理(底层区别)

7.2、内存泄漏

什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:

  • 内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。(内存泄漏是指针丢了)

内存泄漏的危害:

  • 长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死
void MemoryLeaks()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
	// 2.异常安全问题
	int* p3 = new int[10];
	Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
	delete[] p3;
}

内存泄漏分类(了解)

C/C++程序中一般我们关心两种方面的内存泄漏:

堆内存泄漏(Heap leak)

  • 堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

系统资源泄漏

  • 指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

如何检测内存泄漏(了解)

如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结一下:

  • 内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。

7.3、如何一次在堆上申请4G的内存?

// 将程序编译成x64的进程,运行下面的程序试试?
#include <iostream>
using namespace std;
int main()
{
	void* p = new char[0xfffffffful];
	cout << "new:" << p << endl;
	return 0;
}

内容概要:本文档详细介绍了在三台CentOS 7服务器(IP地址分别为192.168.0.157、192.168.0.158和192.168.0.159)上安装和配置Hadoop、Flink及其他大数据组件(如Hive、MySQL、Sqoop、Kafka、Zookeeper、HBase、Spark、Scala)的具体步骤。首先,文档说明了环境准备,包括配置主机名映射、SSH免密登录、JDK安装等。接着,详细描述了Hadoop集群的安装配置,包括SSH免密登录、JDK配置、Hadoop环境变量设置、HDFS和YARN配置文件修改、集群启动与测试。随后,依次介绍了MySQL、Hive、Sqoop、Kafka、Zookeeper、HBase、Spark、Scala和Flink的安装配置过程,包括解压、环境变量配置、配置文件修改、服务启动等关键步骤。最后,文档提供了每个组件的基本测试方法,确保安装成功。 适合人群:具备一定Linux基础和大数据组件基础知识的运维人员、大数据开发工程师以及系统管理员。 使用场景及目标:①为大数据平台建提供详细的安装指南,确保各组件能够顺利安装和配置;②帮助技术人员快速掌握Hadoop、Flink等大数据组件的安装与配置,提升工作效率;③适用于企业级大数据平台的建与维护,确保集群稳定运行。 其他说明:本文档不仅提供了详细的安装步骤,还涵盖了常见的配置项解释和故障排查建议。建议读者在安装过程中仔细阅读每一步骤,并根据实际情况调整配置参数。此外,文档中的命令和配置文件路径均为示例,实际操作时需根据具体环境进行适当修改。
在无线通信领域,天线阵列设计对于信号传播方向和覆盖范围的优化至关重要。本题要求设计一个广播电台的天线布局,形成特定的水平面波瓣图,即在东北方向实现最大辐射强度,在正东到正北的90°范围内辐射衰减最小且无零点;而在其余270°范围内允许出现零点,且正西和西南方向必须为零。为此,设计了一个由4个铅垂铁塔组成的阵列,各铁塔上的电流幅度相等,相位关系可自由调整,几何布置和间距不受限制。设计过程如下: 第一步:构建初级波瓣图 选取南北方向上的两个点源,间距为0.2λ(λ为电磁波波长),形成一个端射阵。通过调整相位差,使正南方向的辐射为零,计算得到初始相位差δ=252°。为了满足西南方向零辐射的要求,整体相位再偏移45°,得到初级波瓣图的表达式为E1=cos(36°cos(φ+45°)+126°)。 第二步:构建次级波瓣图 再选取一个点源位于正北方向,另一个点源位于西南方向,间距为0.4λ。调整相位差使西南方向的辐射为零,计算得到相位差δ=280°。同样整体偏移45°,得到次级波瓣图的表达式为E2=cos(72°cos(φ+45°)+140°)。 最终组合: 将初级波瓣图E1和次级波瓣图E2相乘,得到总阵的波瓣图E=E1×E2=cos(36°cos(φ+45°)+126°)×cos(72°cos(φ+45°)+140°)。通过编程实现计算并绘制波瓣图,可以看到三个阶段的波瓣图分别对应初级波瓣、次级波瓣和总波瓣,最终得到满足广播电台需求的总波瓣图。实验代码使用MATLAB编写,利用polar函数在极坐标下绘制波瓣图,并通过subplot分块显示不同阶段的波瓣图。这种设计方法体现了天线阵列设计的基本原理,即通过调整天线间的相对位置和相位关系,控制电磁波的辐射方向和强度,以满足特定的覆盖需求。这种设计在雷达、卫星通信和移动通信基站等无线通信系统中得到了广泛应用。
评论 79
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三分苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值