c++(三)

1. STL

1.1. 迭代器

  • 迭代器是访问容器中元素的通用方法。如果使用迭代器,不同的容器,访问元素的方法是相同的;
  • 迭代器支持的基本操作:赋值(=)、解引用(*)、比较(==和!=)、从左向右遍历(++)
  • 一般情况下,迭代器是指针和移动指针的方法。

1.1.1. 正向迭代器

只能使用++运算符从左向右遍历容器,每次沿容器向右移动一个元素。

容器名<元素类型>::iterator 迭代器名;        // 正向迭代器。
容器名<元素类型>::const_iterator 迭代器名;  // 常量正向迭代器。// 区别在于通过非常量迭代器还能修改其指向的元素

1.1.2. 双向迭代器

具备正向迭代器的功能,还可以反向(从右到左)遍历容器(也是用++),不管是正向还是反向遍历,都可以用--让迭代器后退一个元素。

容器名<元素类型>:: reverse_iterator 迭代器名;        // 反向迭代器。
容器名<元素类型>:: const_reverse_iterator 迭代器名;  // 常反向迭代器。

1.1.3. 随机访问迭代器

具备双向迭代器的功能,还支持以下操作:

  • 用于比较两个迭代器相对位置的关系运算(<、<=、>、>=);
  • 迭代器和一个整数值的加减法运算(+、+=、-、-=);
  • 支持下标运算(iter[n])。

容器

对应的迭代器类型

array

随机访问迭代器

vector

随机访问迭代器

deque

随机访问迭代器

list

双向迭代器

set / multiset

双向迭代器

map / multimap

双向迭代器

forward_list

前向迭代器

unordered_map / unordered_multimap

前向迭代器

unordered_set / unordered_multiset

前向迭代器

stack

不支持迭代器

queue

不支持迭代器

1.1.4. 迭代器失效

erase方法和insert方法都会导致迭代器失效

  • 当容器调用erase方法后,当前位置到容器末尾元素的所有的迭代器全部失效了;
  • 当容器调用insert方法后,当前位置到容器末尾元素的所有的迭代器全部失效了

1.2. 容器

1.2.1. unordered_map

特性操作

  • size_t bucket_count(); // 返回容器桶的数量,空容器有8个桶。
  • float load_factor(); // 返回容器当前的装填因子,load_factor() = size() / bucket_count()。
  • float max_load_factor(); // 返回容器的最大装填因子,达到该值后,容器将扩充,缺省为1。
  • void max_load_factor (float z ); // 设置容器的最大装填因子。
#include<iostream>
#include<unordered_map>
#include<map>using namespace std;template<class K, class V>
//取别名
using umap = unordered_map<K, V>;int main()
{//umap<int, string> m;//cout << m.bucket_count() << endl;		// 输出默认桶的数量//size_t itmp = m.bucket_count();//for (int i = 0; i < 200000; i++)//{//	char name[50];//	sprintf_s(name, "西施%d", i);		// 将数据格式化输出到字符串//	m.emplace(i, name);					// 向map中插入元素//}//if (itmp != m.bucket_count())//{//	cout << m.bucket_count() << endl;//	itmp = m.bucket_count();//}umap<int, string> m1;cout << "最大装填因子:" << m1.max_bucket_count() << endl;m1.insert({ { 1, "西施1" }, { 2, "西施2" }, { 3, "西施3" }, { 4, "西施4" } });cout << "当前桶数:"	<<  m1.bucket_count() << endl;cout << "当前装填因子:" << m1.load_factor() << endl;m1.insert({ { 5, "西施5" }, { 6, "西施6" }, { 7, "西施7" }, { 8, "西施8" } });cout << "当前桶数:" << m1.bucket_count() << endl;cout << "当前装填因子:" << m1.load_factor() << endl;m1.emplace(9, "西瓜");cout << "当前桶数:" << m1.bucket_count() << endl;cout << "当前装填因子:" << m1.load_factor() << endl;
}
  • iterator begin(size_t n); // 返回第n个桶中第一个元素的迭代器。
  • iterator end(size_t n); // 返回第n个桶中最后一个元素尾后的迭代器。
#include<iostream>
#include<string>
#include<unordered_map>
#include<map>using namespace std;template<class K, class V>
//取别名
using umap = unordered_map<K, V>;int main()
{umap<int, string> m1;m1.max_load_factor(5);m1.insert({ { 1, "西施1" }, { 2, "西施2" }, { 3, "西施3" }, { 4, "西施4" } });m1.insert({ { 5, "西施5" }, { 6, "西施6" }, { 7, "西施7" }, { 8, "西施8" } });m1.insert({ { 9, "西施9" }, { 10, "西施10" }, { 11, "西施11" }, { 12, "西施12" } });m1.insert({ { 13, "西施13" }, { 14, "西施14" }, { 15, "西施15" }, { 16, "西施16" } });m1.emplace(17, "西瓜");// 遍历整个容器的方式1for (auto& item : m1){cout << item.first << "," << item.second << " ";}// 遍历整个容器的方式2for (auto it = m1.begin(); it != m1.end(); it++){cout << it->first << "," << it->second << " ";}cout << endl;// 遍历桶中的元素for (int i = 0; i < m1.bucket_count(); i++){cout << "桶" << i << ": ";for (auto it = m1.begin(i); it != m1.end(i); it++){cout << it->first << "," << it->second << " ";}cout << endl;}
}
  • void reserve(size_t n); // 将容器设置为至少n个桶。
  • void rehash(size_t n); // 将桶的数量调整为>=n。如果n大于当前容器的桶数,该方法会将容器重新哈希;如果n的值小于当前容器的桶数,该方法可能没有任何作用。
  • size_t bucket_size(size_t n); // 返回第n个桶中元素的个数,0 <= n < bucket_count()。
  • size_t bucket(K &key); // 返回值为key的元素对应的桶的编号。

1.3. 算法

1.3.1. for_each

模拟实现for_each算法的功能。特别是要掌握仿函数的用法。

//
//  main.cpp
//  foreach_demo
//
//  Created by apple on 2023/10/5.
//#include <iostream>
#include <vector>
using namespace std;// 模板函数
template<typename T>
void show(const T& t)
{cout << "亲爱的 " << t << " 号,我是一只傻傻鸟" << endl;
}// 仿函数
template<class T>
class CGirl
{
public:CGirl(){};void operator()(const T& t){cout << "亲爱的 " << t << " 号,我是一只傻傻鸟" << endl;}
};
// 自定义的foreach函数,用于遍历容器,并对每个元素执行操作
/**T2 func:func是一个函数指针,函数指针的类型是T2*/
template<typename T1, typename T2>
void foreach(const T1& start, const T1& end, T2 func)
{for(auto it=start; it!=end;it++){func(*it);}
}int main(int argc, const char * argv[]) {// 存放int的vector容器vector<int> v = {3,4,23,2,1};foreach(v.begin(),  v.end(), show<int>);cout << endl;// 显式地创建对象CGirl<int> c;foreach(v.begin(),  v.end(), c);// 匿名对象的括号不能省略foreach(v.begin(),  v.end(), CGirl<int>());return 0;
}

1.3.2. find_if

模拟实现find_if算法的功能

//
//  main.cpp
//  findif_demo
//
//  Created by apple on 2023/10/5.
//#include <iostream>
#include <vector>
#include <list>using namespace std;// 使用模板函数
template<typename T>
bool show(const T& t, const T& no)
{return t == no;
}// 使用仿函数
template<class T>
class CGirl
{
public:T m_no;CGirl(T no):m_no(no){};bool operator()(const T& t){return t == m_no;}
};// 自定义find_if函数,使用模板函数,返回的是一个迭代器
template<typename T1, typename T2, typename T3>
T1 findif(const T1& start, const T1& end, T2 pfunc, T3 no)
{for(auto it = start; it!=end;it++){if (pfunc(*it, no)){return it;}}return end;
}// 自定义find_if函数,使用仿函数,返回的是一个迭代器
template<typename T1, typename T2>
T1 findif(const T1& start, const T1& end, T2 pfunc)
{for(auto it = start; it!=end;it++){if (pfunc(*it)){return it;}}return end;
}int main(int argc, const char * argv[]) {vector<int> v1 {1,2,3,4,5,7};list<string> v2 {"05", "07", "08"};auto t1 = findif(v2.begin(), v2.end(), show<string>, "03");auto t2 = findif(v2.begin(), v2.end(), CGirl<string>("03"));if(t2 == v2.end()){cout << "查找失败" << endl;}else{cout << "查找成功:" << *t2 << endl;}return 0;
}

2. 对象优化

2.1. 对象构造时的对象优化

不同方式构造对象,其背后都调用了哪些方法呢?

#include <iostream>using namespace std;class Test
{
public:Test(int a=10) :ma(a){cout << "Test(int)" << endl;}~Test(){cout << "~Test()" << endl;}Test(const Test& t){cout << "Test(const Test&)" << endl;ma = t.ma;}Test& operator=(const Test& t){cout << "operator=" << endl;ma = t.ma;return *this;}
private:int ma;
};
#if 0int main()
{Test t1;Test t2(t1);Test t3 = t1;// 等价于Test t4(20)Test t4 = Test(20);	 // c++编译器对对象构造做了优化,这里的临时对象不产生了,直接构造新对象。不会打印Test(const Test&),只会打印Test(int)t4 = Test(40);		// 这里就必须要生成临时对象了,因为这行代码调用的是赋值函数,需要将临时对象当做参数传递给operator=函数cout << "-------------------" << endl;// 指针指向对象的优化//Test* p = &Test(40); // 不能用一个指针去保留一个临时对象的地址,这样会造成野指针Test&&ri = Test(40); // 这样是可以的。Test(40)是一个临时对象,是一个右值,需要使用右值引用
}#endif // 1

上面的代码中,Test t4 = Test(20)这行代码,按照正常的构造对象方式是,Test(20)生成一个临时对象(即调用构造方法);然后将这个临时对象拷贝构造给t4(即调用拷贝构造方法)。但是因为Test(20)生成的是一个临时对象,出了这行代码之后就被销毁了。因此编译器在这里就做了优化,不再产生这个临时对象,而是直接构造t4新对象。因此Test t4 = Test(20)等价于Test t4(20)

如果想要看到编译器未做优化的打印结果,可以在 Linux 系统下设置set(CMAKE_CXX_FLAGS -fno-elide-constructors)

添加带右值引用参数的拷贝构造函数和赋值函数

#include <iostream>
using namespace std;class CMyString
{
public:// 构造函数CMyString(const char* p = nullptr){if (p != nullptr){mptr = new char[strlen(p) + 1];		// 开辟空间并初始化strcpy(mptr, p);  //  将p空间中的内容拷贝到mptr}// 防止mptr是一个空指针else {mptr = new char[1];*mptr = '\0';}cout << "CMyString()" << endl;}// 析构函数~CMyString(){delete[]mptr;mptr = nullptr;		// 标准写法,释放空间后再将指针指向空,因为释放空间后,指针还保留着地址cout << "~CMyString()" << endl;}// 拷贝构造函数CMyString(const CMyString& str) // 传入的是一个左值对象,即有地址或者有名字的对象{mptr = new char[strlen(str.mptr) + 1];strcpy(mptr, str.mptr);cout << "CMyString(const CMyString&)" << endl;}// 拷贝构造函数CMyString(CMyString&& str) // 传入的是一个右值对象,即没有地址或者没有名字的对象{mptr = str.mptr;str.mptr = nullptr;		// 要置为空,不然两个指针都指向同一个对象,在对象析构的时候会报错cout << "CMyString(CMyString&&)" << endl;}// 赋值构造函数CMyString& operator=(CMyString&& str){cout << "String& operator=(String&&)" << endl;// 防止自赋值if (this == &str){return *this;}delete[] mptr; // 先把原先的内存释放掉mptr = str.mptr;str.mptr = nullptr;return *this;}const char* c_str() const { return mptr; }
private:char* mptr;friend CMyString operator+ (const CMyString& str1, const CMyString& str2);friend ostream& operator<<(ostream& out, const CMyString& str);
};CMyString operator+ (const CMyString& str1, const CMyString& str2)
{cout << "operator+" << endl;CMyString tempstr;		//CMyString()tempstr.mptr = new char[strlen(str1.mptr) + strlen(str2.mptr) + 1];strcpy(tempstr.mptr, str1.mptr);strcat(tempstr.mptr, str2.mptr);return tempstr;	
}ostream& operator<<(ostream& out, const CMyString& str)
{out << str.mptr;return out;
}
#if 1
int main()
{CMyString str1 = "aaaaaaaaaaaaaaaaa";		//CMyString()CMyString str2 = "bbbbbbbbbbbbbb";			//CMyString()CMyString str3 = str1 + str2;				//operator+,CMyString(),CMyString(CMyString&&)cout << str3 << endl;cout << "--------------------" << endl;
}
#endif

打印结果如下:

CMyString()
CMyString()
operator+
CMyString()
CMyString(CMyString&&)	// 直接使用临时对象tempstr拷贝构造str3对象
~CMyString()						// 析构掉临时对象tempstr
aaaaaaaaaaaaaaaaabbbbbbbbbbbbbb
--------------------
~CMyString()
~CMyString()
~CMyString()

2.2. 函数调用时的对象优化

正常代码

#include <iostream>using namespace std;class Test
{
public:Test(int a = 10) :ma(a){cout << "Test(int)" << endl;}~Test(){cout << "~Test()" << endl;}Test(const Test& t){cout << "Test(const Test&)" << endl;ma = t.ma;}Test& operator=(const Test& t){cout << "operator=" << endl;ma = t.ma;return *this;}int getData()const { return ma; }
private:int ma;
};Test getObject(Test t)
{int val = t.getData();Test tmp(val);return tmp;
}#if 1
int main()
{Test t1;Test t2;t2 = getObject(t1);
}
#endif 

上面代码中,执行过程如下:

  • Test t1; 会调用构造方法,构造t1对象。即打印Test(int)
  • Test t2; 会调用构造方法,构造t2对象。即打印Test(int)
  • t2 = getObject(t1); 调用getObject函数,会将t1传递给形参t,这里有一个拷贝构造,即打印Test(const Test&)。然后在getObject函数中,会创建tmp对象,即打印Test(int)。然后return tmp,为了将临时对象带出getObject函数,会调用拷贝构造函数在main函数的栈上创建一个临时对象(tmp1),即打印Test(const Test&)。出了getObject函数后,tmp对象就会被销毁,即打印~Test()。形参t也会被析构,即打印~Test()。然后将临时对象(tmp1)赋值给t2对象,即打印operator=。之后销毁临时对象(tmp1),即打印~Test()
  • 最后依次销毁对象t2和t1。即打印~Test(),~Test()
Test(int)
Test(int)
Test(const Test&)
Test(int)
Test(const Test&)
~Test()
~Test()
operator=
~Test()
~Test()
~Test()

优化代码

#include <iostream>using namespace std;class Test
{
public:Test(int a = 10) :ma(a){cout << "Test(int)" << endl;}~Test(){cout << "~Test()" << endl;}Test(const Test& t){cout << "Test(const Test&)" << endl;ma = t.ma;}Test& operator=(const Test& t){cout << "operator=" << endl;ma = t.ma;return *this;}int getData()const { return ma; }
private:int ma;
};Test getObject(Test &t)
{int val = t.getData();return Test(val);
}#if 1
int main()
{Test t1;Test t2 = getObject(t1);
}
#endif 

优化后的代码,执行过程如下:

  • Test t1;会调用构造方法,构造t1对象。即打印Test(int)
  • Test t2 = getObject(t1);由于getObject函数的形参接收的是Test对象的引用,因此这里就不会再拷贝对象了。进入getObject函数内,return Test(val)这行代码,这里编译器做了优化,不再产生临时对象 Test(val),而是直接构造t2对象,即打印Test(int)
  • 最后依次销毁对象t2和t1。即打印~Test(),~Test()

打印结果如下:

Test(int)
Test(int)
~Test()
~Test()

2.3. 移动语义与完美转发

右值引用变量本身还是一个左值。如T &&val,val本身还是一个左值,因为它也有地址。

std::move(),将左值强转成右值

std::forward<T>,类型完美转发,能够识别左值和右值类型

以下是一个vector容器存放自定义对象的例子

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;class CMyString1
{
public:CMyString1(const char* p = nullptr){if (p != nullptr){mptr = new char[strlen(p) + 1];strcpy(mptr, p);}else{mptr = new char[1];*mptr = '\0';}cout << "CMyString()" << endl;}~CMyString1(){delete[] mptr;mptr = nullptr;cout << "~CMyString()" << endl;}CMyString1(const CMyString1& str){mptr = new char[strlen(str.mptr) + 1];strcpy(mptr, str.mptr);cout << "CMyString(const CMyString&)" << endl;}CMyString1(CMyString1&& str){mptr = str.mptr;str.mptr = nullptr;cout << "CMyString(CMyString&&)" << endl;}CMyString1& operator=(CMyString1&& str){cout << "String& operator=(String&&)" << endl;if (this == &str)return *this;delete[] mptr;mptr = str.mptr;str.mptr = nullptr;return *this;}const char* c_str() const { return mptr; }private:char* mptr;friend CMyString1 operator+(const CMyString1& lhs,const CMyString1& rhs);friend ostream& operator<<(ostream& out, const CMyString1& str);
};CMyString1 GetString(CMyString1& str)
{const char* pstr = str.c_str();CMyString1 tmpStr(pstr);cout << "--------------------" << endl;return tmpStr;
}CMyString1 operator+(const CMyString1& lhs,const CMyString1& rhs)
{cout << "--------------------" << endl;// char* ptmp = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];CMyString1 tmpStr;tmpStr.mptr = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];strcpy(tmpStr.mptr, lhs.mptr);strcat(tmpStr.mptr, rhs.mptr);return tmpStr;
}ostream& operator<<(ostream& out, const CMyString1& str)
{out << str.mptr;return out;
}// 容器的空间配置器allocator做四件事情  内存开辟/内存释放  对象构造/对象析构
template<typename T>
struct Allocator
{T* allocate(size_t size) // 负责内存开辟{return (T*)malloc(sizeof(T) * size);}void deallocate(void* p) // 负责内存释放{free(p);}//void construct(T* p, const T& val) // 负责对象构造//{//	new (p) T(val); // 定位new//}//void construct(T* p, T&& val) // 负责对象构造//{//	new (p) T(std::move(val)); // 定位new//}template<typename Ty>void construct(T* p, Ty&& val){new (p) T(std::forward<Ty>(val));}void destroy(T* p) // 负责对象析构{p->~T();}
};/*
容器底层内存开辟,内存释放,对象构造和析构,都通过allocator空间配置器来实现
*/
template<typename T, typename Alloc = Allocator<T>>
class vector
{
public:vector(int size = 10){// 需要把内存开辟和对象构造分开处理// _first = new T[size];_first = _allocator.allocate(size);_last = _first;_end = _first + size;}~vector(){// 析构容器有效的元素,然后释放_first指针指向的堆内存// delete[] _first;for (T* p = _first; p != _last; ++p) // 把有效的对象析构从_first到_last{_allocator.destroy(p);}_allocator.deallocate(_first); // 释放内存_first = _end = _last = nullptr;}vector(const vector<T>& rhs){int size = rhs._end - rhs._first;//_first = new T[size];_first = _allocator.allocate(size);int len = rhs._last - rhs._first;for (int i = 0; i < len; ++i){//_first[i] = rhs._first[i];_allocator.construct(_first + i, rhs._first[i]);}_last = _first + len;_end = _first + size;}vector<T>& operator = (const vector<T>& rhs){if (this == &rhs){return *this;}//delete[] _first;for (T* p = _first; p != _last; ++p) // 把有效的对象析构从_first到_last{_allocator.destroy(p);}_allocator.deallocate(_first);int size = rhs._end - rhs._first;//_first = new T[size];_first = _allocator.allocate(size);int len = rhs._last - rhs._first;for (int i = 0; i < len; ++i){//_first[i] = rhs._first[i];_allocator.construct(_first + i, rhs._first[i]);}_last = _first + len;_end = _first + size;return *this;}//void push_back(const T& val)//{//	if (full())//		expand();//	//*_last++ = val;  _last指针指向的内存构造一个值为val的对象//	_allocator.construct(_last, val);//	_last++;//}//void push_back(T&& val)//{//	if (full())//		expand();//	_allocator.construct(_last, std::move(val));//	_last++;//}template<typename Ty> // 函数模板的类型推演+引用折叠void push_back(Ty&& val) // 引用折叠,& + && = &, && + && = &&{if (full())expand();// move:移动语义,得到右值类型// forward:类型完美转发,能够识别左值和右值类型_allocator.construct(_last, std::forward<Ty>(val));_last++;}void pop_back(){if (empty())return;//--_last;   不仅要把_last指针--,还需要析构删除的元素--_last;_allocator.destroy(_last);}T back() const{return *(_last - 1);}bool full() const { return _last == _end; }bool empty() const { return _first == _last; }int size() const { return _last - _first; }
private:T* _first;T* _last;T* _end;Alloc _allocator;void expand(){int size = _end - _first;//T* ptmp = new T[2 * size];T* ptmp = _allocator.allocate(2 * size);for (int i = 0; i < size; i++){_allocator.construct(ptmp + i, _first[i]);//ptmp[i] = _first[i];}//delete[] _first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p);}_allocator.deallocate(_first);_first = ptmp;_last = _first + size;_end = _first + 2 * size;}
};class Test
{
public:Test(int a = 10) : ma(a) { cout << "Test()" << endl; }~Test() { cout << "~Test()" << endl; }Test(const Test& t){ma = t.ma;cout << "Test(const Test& t)" << endl;}
public:int ma;
};#if 1
int main()
{CMyString1 str1 = "aaa";vector<CMyString1> vec;cout << "------------------" << endl;vec.push_back(str1);vec.push_back(CMyString1("bbbb"));cout << "------------------" << endl;return 0;
}
#endif

3. 智能指针

3.1. 不带引用计数的智能指针

3.1.1. unique_ptr

unique_ptr独享它指向的对象,也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,指向的对象也随即被销毁。

unique_ptr是一个模板类,里面有一个内置的指针来指向对象。

使用unique_str需要包含头文件:#include <memory>

3.1.1.1. move
#include <iostream>
#include <string>
#include <memory>int main() {std::unique_ptr<int> p1(new int(10));std::unique_ptr<int> p2 = std::move(p1);std::cout << "p2: " << *p2 << std::endl;// std::cout << "p1: " << *p1 << std::endl; // errorreturn 0;
} // p2: 10
3.1.1.2. release
#include <iostream>
#include <string>
#include <memory>int main()
{std::unique_ptr<int> auto_pointer(new int);int * manual_pointer;*auto_pointer = 10;// 转移该对象到另一个指针那里去manual_pointer = auto_pointer.release();// (auto_pointer is now empty)std::cout << "manual_pointer points to " << *manual_pointer << '\n';std::cout << "auto_pointer points to " << (auto_pointer ? "not null" : "nullptr") << '\n';delete manual_pointer;return 0;
}// manual_pointer points to 10
// auto_pointer points to nullptr
3.1.1.3. swap
#include <iostream>
#include <string>
#include <memory>int main() {std::unique_ptr<int> p1(new int(10));std::unique_ptr<int> p2(new int(20));p1.swap(p2);std::cout << "p1: " << *p1 << std::endl;std::cout << "p2: " << *p2 << std::endl;return 0;
}// p1: 20
// p2: 10
3.1.1.4. reset
#include <iostream>
#include <string>
#include <memory>using namespace std;class AA
{
public:string m_name;AA(){cout << "调用AA的无参构造函数" << endl;}AA(string name):m_name(name){cout << "调用AA的有参构造函数" << endl;}~AA(){cout << "调用AA的析构函数" << endl;}
};int  main()
{// 方法1unique_ptr<AA>p0(new AA());// 方法2unique_ptr<AA> p1 = unique_ptr<AA>(new AA("西施"));// 释放对象p1.reset();cout << "auto_pointer points to " << (p1 ? "not null" : "nullptr") << '\n';
}//调用AA的无参构造函数
//调用AA的有参构造函数
//调用AA的析构函数
//auto_pointer points to nullptr
//调用AA的析构函数

3.2. 带引用计数的智能指针

3.2.1. shared_ptr

4. 内存划分

4.1. 代码区

  • 存放函数体的二进制代码,由操作系统进行管理;
  • 代码区是共享的,共享的目的是对频繁被执行的程序,只需要在内存中有一份代码即可;
  • 代码区是只读的,防止程序意外修改了它的指令;
  • 属于程序运行前的区域

4.2. 全局/静态区

  • 存放全局变量、静态变量
  • 内存空间由操作系统释放;
  • 属于程序运行前的区域

4.3. 文字常量区

  • 存放字符串常量
  • 常量区是只读

4.4. 栈区

  • 编译器自动分配和释放,存放函数的参数值局部变量等;
  • 特别注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

4.5. 堆区

  • 程序员分配和释放(释放内存使用delete关键字),若程序员不释放,程序结束后由操作系统回收;
  • 在c++中主要利用new关键字在堆区开辟内存
#include<iostream>
#include<string>using namespace std;// 在堆区创建一个变量
void test()
{int* i = new	 int(10);cout << *i << endl;// 释放new分配的内存空间,不能连续多次释放同一块内存空间delete i;// 在释放掉内存后,手动设置指针指向空,后续再继续对空指针释放内存是安全的i = nullptr;
}// 在堆区创建一个数组
void test1()
{int* arr = new	 int[10];// 给数组赋值for (int i = 0; i < 10; i++){arr[i] = i + 10;}// 打印数组for (int i = 0; i < 10; i++){cout << arr[i] << endl;}delete[] arr;arr = nullptr;
}
int main()
{test();test1();
}
#include <iostream>
using namespace std;int num1;
int* ptr1;int main(int argc, const char* argv[]) {int num2;char* ptr2;double scores[]{ 3.1,3.2 };char str1[10] = "我是谁";const char* str2  = "我是谁"; static int num3(10);ptr1 = new int(3);ptr2 = new char[10];cout  << "num1的地址:\t" << &num1 << endl;cout << "scores的地址:\t" << scores << endl;printf("str1的地址:\t%p\n", str1);cout << "&str1的地址:\t" << &str1 << endl;printf("str2的地址:\t%p\n", str2);cout << "&str2的地址:\t" << &str2 << endl;cout << "num2的地址:\t" << &num2 << endl;cout << "num3的地址:\t" << &num3 <<endl;cout << "ptr1的地址:\t" << ptr1 << endl;printf("ptr2的地址:\t%p\n", ptr2);return 0;
}
num1的地址:    00007FF6220EF9E0		全局区
scores的地址:  000000DCBD4FF888		栈区
str1的地址:    000000DCBD4FF8B8		栈区
&str1的地址:   000000DCBD4FF8B8		栈区
str2的地址:    00007FF6220EBD0C		文字常量区
&str2的地址:   000000DCBD4FF8E8		栈区
num2的地址:    000000DCBD4FF844		栈区
num3的地址:    00007FF6220EF010		全局区
ptr1的地址:    000002AFC90623F0		堆区
ptr2的地址:    000002AFC90638E0		堆区

5. 代码编译原理

5.1. 预编译

以#开头的命令(除开 pragma lib、pragma link 等发生在链接阶段的指令)

预编译命令为: g++ -E 需要预处理的cpp文件 -o 处理后需要保存的文件名

(base) apple@appledeMacBook-Pro lesson_1 % ls
CMakelists.txt  add.cpp         add.h           main.cpp
(base) apple@appledeMacBook-Pro lesson_1 % g++ -E add.cpp -o add.i

5.2. 编译

将 c++文件编译成对应平台下的汇编指令。编译过程中,符号是不生成虚拟地址的。

编译命令为:g++ -E 原始的cpp文件 -o 处理后需要保存的文件名 或者 g++ -E 预处理之后的文件 -o 处理后需要保存的文件名

(base) apple@appledeMacBook-Pro lesson_1 % g++ -E add.cpp -o add.i
(base) apple@appledeMacBook-Pro lesson_1 % g++ -S add.i -o add.s或者
(base) apple@appledeMacBook-Pro lesson_1 % g++ -S add.cpp -o add.s

5.3. 汇编

将汇编指令转换成机器码,也就是计算机硬件可以直接执行的指令。这一步产生的文件叫做目标文件(main.o),是二进制格式。

汇编命令为:g++ -c 原始的cpp文件 -o 处理后需要保存的文件名 或者 g++ -c 编译之后的文件 -o 处理后需要保存的文件名

.o 文件的头文件中包含了程序的入口地址。

.o 文件的格式组成如下:

5.4. 链接

链接命令为:g++ 所有的app文件 -o 输出的文件名 或者 g++ 所有的汇编之后的文件 -o 输出的文件名

5.4.1. 文件段合并

所有.o 文件中的.text 段进行合并;所有.o 文件中的.data 段进行合并;所有.o 文件中的.bss 段进行合并;......

所有对符号的引用都要找到该符号定义的地方,如上面的 main.o 文件中的 gdata 符号和 _Z3sumii 符号,要去其他文件中找到定义的地方,找不到的话就链接失败。且也不能重复定义,不然也要报错。

5.4.2. 符号重定向

给所有的符号分配虚拟地址,并将.o 文件中的符号地址进行修改

最后生成可执行文件,可执行文件与.o 文件都是由各种段组成的,只是可执行文件多了一个 program headers 段。

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

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

相关文章

隐私计算实训营第二期第11讲组件介绍与自定义开发

首先给大家推荐一个博主的笔记&#xff1a;隐语课程学习笔记11- 组件介绍与自定义开发-CSDN博客 官方文档&#xff1a;隐语开放标准介绍 | 开放标准 v1.0.dev240328 | 隐语 SecretFlow 01 隐语开放标准 隐语开放标准是为隐私保护应用设计的协议栈。 目前&#xff0c;隐语开…

mysql8.0-学习

文章目录 mysql8.0基础知识-学习安装mysql_8.0登录mysql8.0的体系结构与管理体系结构图连接mysqlmysql8.0的 “新姿势” mysql的日常管理用户安全权限练习查看用户的权限回收:revoke角色 mysql的多种连接方式socket显示系统中当前运行的所有线程 tcp/ip客户端工具基于SSL的安全…

Wp-scan一键扫描wordpress网页(KALI工具系列三十二)

目录 1、KALI LINUX 简介 2、Wp-scan工具简介 3、信息收集 3.1 目标IP&#xff08;服务器) 3.2kali的IP 4、操作实例 4.1 基本扫描 4.2 扫描已知漏洞 4.3 扫描目标主题 4.4 列出用户 4.5 输出扫描文件 4.6 输出详细结果 5、总结 1、KALI LINUX 简介 Kali Linux 是一…

STM32CubeIDE使用标准库

以STM32F030为例使用标准库文件。 1、新建工程&#xff0c;选择需要使用配置的型号。 2、在工程选型中&#xff0c;选择新建空工程。 3、新建空工程 新建完成&#xff0c;系统只有main函数和启动文件.s有用。 4、启动文件 启动文件&#xff0c;同样是一个汇编文件&#xf…

【Matlab函数分析】imread从图形文件读取图像

&#x1f517; 运行环境&#xff1a;Matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

如何利用AI生成可视化图表(统计图、流程图、思维导图……)免代码一键绘制图表

由于目前的AI生成图表工具存在以下几个方面的问题&#xff1a; 大多AI图表平台是纯英文&#xff0c;对国内用户来说不够友好&#xff1b;部分平台在生成图表前仍需选择图表类型、配置项&#xff0c;操作繁琐&#xff1b;他们仍需一份规整的数据表格&#xff0c;需要人为对数据…

软件框架(Framework)是什么?

可实例化的、部分完成的软件系统或子系统&#xff0c;它为一组系统或子系统定义了统一的体系结构(architecture)&#xff0c;并提供了构造系统的基本构造块(building blocks)&#xff0c;还为实现具体功能定义了扩展点(extending points)。 框架实现了体系结构级别的复用。 其…

系统工程与信息系统基础(上)

目录 系统工程 霍尔三维结构的三维&#xff1a; 切克兰德方法&#xff1a; 并行工程方法&#xff1a; 综合集成法&#xff1a; WSR系统方法&#xff1a; 系统工程生命周期阶段 探索性阶段 概念阶段 开发阶段 生产阶段 使用阶段 保障阶段 退役阶段 系统工程生命周…

初识HTML

HTML语法规范 1、HTML标签是由尖括号包围的关键字&#xff0c;例如<html>。 2、HTML标签通常成对出现&#xff0c;例如<html></html>&#xff0c;此为双标签&#xff0c;标签对的第一个标签是开始标签&#xff0c;第二个标签是结束标签。 3、有些特殊标签…

详解flink sql, calcite logical转flink logical

文章目录 背景示例FlinkLogicalCalcConverterBatchPhysicalCalcRuleStreamPhysicalCalcRule其它算子FlinkLogicalAggregateFlinkLogicalCorrelateFlinkLogicalDataStreamTableScanFlinkLogicalDistributionFlinkLogicalExpandFlinkLogicalIntermediateTableScanFlinkLogicalInt…

语音唤醒入门(基于ESP-skainet)

主要参考资料&#xff1a; ESP-SR 用户指南: https://docs.espressif.com/projects/esp-sr/zh_CN/latest/esp32s3/index.html 目录 ESP提供的模型直接初始化和使用模型AFE声学前端算法 使用模型 自定义模型 ESP提供的模型 乐鑫提供了经过训练的 WakeNet 和 MultiNet 模型&…

45.分解质因数

上海市计算机学会竞赛平台 | YACSYACS 是由上海市计算机学会于2019年发起的活动,旨在激发青少年对学习人工智能与算法设计的热情与兴趣,提升青少年科学素养,引导青少年投身创新发现和科研实践活动。https://www.iai.sh.cn/problem/711 题目描述 给定一个整数 𝑛n,请将它…

HDFS详细介绍以及HDFS集群环境部署【hadoop组件HDFS笔记】(图片均为学习时截取的)

HDFS详细介绍 HDFS是什么 HDFS是Hadoop三大组件(HDFS、MapReduce、YARN)之一 全称是&#xff1a;Hadoop Distributed File System&#xff08;Hadoop分布式文件系统&#xff09;&#xff1b;是Hadoop技术栈内提供的分布式数据存储解决方案 可以在多台服务器上构建存储集群&…

云计算【第一阶段(21)】Linux引导过程与服务控制

目录 一、linux操作系统引导过程 1.1、开机自检 1.2、MBR引导 1.3、GRUB菜单 1.4、加载 Linux 内核 1.5、init进程初始化 1.6、简述总结 1.7、初始化进程centos 6和7的区别 二、排除启动类故障 2.1、修复MBR扇区故障 2.1.1、 实验 2.2、修复grub引导故障 2.2.1、实…

敏捷开发笔记(第9章节)--开放-封闭原则(OCP)

目录 1&#xff1a;PDF上传链接 9.1 开放-封闭原则&#xff08;OCP&#xff09; 9.2 描述 9.3 关键是抽象 9.3.1 shape应用程序 9.3.2 违反OCP 糟糕的设计 9.3.3 遵循OCP 9.3.4 是的&#xff0c;我说谎了 9.3.5 预测变化和“贴切的”结构 9.3.6 放置吊钩 1.只受一次…

团队任务管理跟踪软件有哪些?分享2024年值得关注的10款

本文将分享2024年值得关注的10款团队任务管理跟踪软件&#xff1a;Worktile、PingCode、Zoho Projects、Wrike、ProofHub、Connecteam、MeisterTask、Nifty、BIGContacts、Hive。 无论是小型初创企业还是庞大的跨国公司&#xff0c;高效的任务管理都能显著提升工作效率&#xf…

面试框架一些小结

springcloud的⼯作原理 springcloud由以下⼏个核⼼组件构成&#xff1a; Eureka&#xff1a;各个服务启动时&#xff0c;Eureka Client都会将服务注册到Eureka Server&#xff0c;并且Eureka Client还可以反过来从Eureka Server拉取注册表&#xff0c; 从⽽知道其他服务在哪⾥ …

新能源行业知识体系-------主目录-----持续更新

本文相当于目录方便快速检索内容&#xff0c;没有实际内容&#xff0c;只做索引 文章目录 一、电力市场概论二、蒙西电网需求侧响应三、蒙西电网市场结算V2.0 一、电力市场概论 是学习清华大学电力市场概论(2024年春)的学习笔记&#xff0c;详细了解电力市场是如何利用经济学知…

48 - 按日期分组销售产品(高频 SQL 50 题基础版)

48 - 按日期分组销售产品 -- group_concat 分组拼接selectsell_date,count(distinct product) num_sold,group_concat(distinct product order by product separator ,) products fromActivities group bysell_date;

grpc教程——proto文件转go

【1】编写一个proto文件 syntax "proto3"; package myproto;service NC{rpc SayStatus (NCRequest) returns (NCResponse){} }message NCRequest{ string name 1; } message NCResponse{string status 1; } 【2】转换&#xff1a;protoc --go_out. myservice.pro…