C++ primer 第12章 动态内存

文章目录

  • 前言
  • 动态内存与智能指针
    • shared_ptr类
      • shared_ptr和unique_ptr都支持的操作
      • shared_ptr独有的操作
      • make_shared 函数
      • shared_ptr的拷贝和赋值
      • shared_ptr自动销毁所管理的对象
      • shared_ptr还会自动释放相关联的内存
      • 程序使用动态内存出于以下原因
    • 直接管理内存
      • 使用new动态分配和初始化对象
      • 动态分配的const对象
      • 内存耗尽 定位new
      • 释放动态内存
      • 使用new和delete管理动态内存存在三个常见问题
      • delete之后重置指针值
      • 智能指针和new对比
    • shared_ptr和new结合使用
      • 定义和改变shared_ptr的其他方法
      • 不要混合使用智能指针和普通指针
      • 不要使用get初始化另一个智能指针或为智能指针赋值
    • 智能指针和异常
      • 使用自己定义的释放操作
      • 使用智能指针基本规范
    • unique_ptr
      • unique_ptr特有的操作
      • release返回指针
      • 传递unique_ptr参数和返回unique_ptr
      • 向unique_ptr传递删除器
    • weak_ptr
  • 动态数组
    • new和数组
      • 释放动态数组
      • 智能指针和动态数组
      • 指向数组的unique_ptr
      • 连接两个字符串
    • allocator类
      • 标准库allocator类及其算法
      • allocator分配未构造的内存
      • allocator算法

前言

静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。

除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称为自由空间或堆。程序用堆来存储动态分配的对象——即,那些在程序运行时分配的对象。动态对象的生存期由程序来控制,也就是说,当动态对象不再使用时,我们的代码必须显式地销毁它们。

动态内存与智能指针

动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针;delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

动态内存的使用很容易出现问题,因为确保在正确的时间释放内存是及其困难的。忘记释放内存,会产生内存泄漏;在尚有指针引用内存的情况下我们就释放了内存,会产生引用非法内存的指针。

新标准库提供了两种智能指针类型来管理动态对象,它可以自动释放所指向的对象。shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象。weak_ptr是一种弱引用,指向shared_ptr所管理的对象,这三种类型都定义在memory头文件中。

shared_ptr类

当我们创建一个智能指针时,必须提供额外的信息——指针可以指向的类型。与vector一样,我们在尖括号内给出类型,之后是所定义的这种智能指针的名字:

shared_ptr<string>p1;		shared_ptr,可以指向string
shared_ptr<list<int>>p2;	shared_ptr,可以指向list<int>

默认初始化的智能指针中保存着一个空指针。

shared_ptr和unique_ptr都支持的操作

在这里插入图片描述

shared_ptr独有的操作

在这里插入图片描述

make_shared 函数

p3指向一个值为42的int的智能指针
shared_ptr<int>p3 = make_shared<int>(42);p4指向一个值为"88888"的string的智能指针
shared_ptr<string>p4 = make_shared<string>(5,'8');p5指向一个值为0的int的智能指针(int的初始化值为0)
shared_ptr<int>p5 = make_shared<int>();

shared_ptr的拷贝和赋值

当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。离开作用域计数器会递减,赋值操作,原指针计算器会递减,赋值指向的指针计数器会递增。示例代码如下:

	auto p = make_shared<int>(42);//p指向的对象只有p一个引用者auto r = make_shared<int>(4);//p指向的对象只有p一个引用者auto r2(r);//p指向的对象只有p一个引用者cout << "p引用数量:" << p.use_count()<<endl;{auto q(p);//p和q指向相同对象,此对象有两个引用者cout << "p引用数量:" << p.use_count() << endl;cout << "p指向:" << *p << endl;*q = 10;cout << "p指向:" << *p << endl;}cout << "在赋值之前,p引用数量:" << p.use_count() << endl;cout << "在赋值之前,原r引用数量:" << r2.use_count() << endl;r = p;cout << "在赋值之后,原r引用数量:" << r2.use_count() << endl;cout << "在赋值之后,p引用数量:" << p.use_count() << endl;cout << "p指向:" << *p << endl;

输出结果:

p引用数量:1
p引用数量:2
p指向:42
p指向:10
在赋值之前,p引用数量:1
在赋值之前,原r引用数量:2
在赋值之后,原r引用数量:1
在赋值之后,p引用数量:2
p指向:10

shared_ptr自动销毁所管理的对象

当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会通过析构函数自动销毁此对象。shared_ptr的析构函数会递减它所指向的对象的引用计数,如果引用计数为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

shared_ptr还会自动释放相关联的内存

当动态对象不再被使用时,shared_ptr会自动释放动态对象,这一特性使得动态内存的使用变得非常容易。
假设现有一个Foo类,示例代码:

//factory返回一个shared_ptr,指向一个动态分配的对象
shared_ptr<Foo> factory(T arg){return make_shared<Foo>(arg);
}void use_factory(T arg){shared_ptr<Foo>p=factory(arg);//使用p
}//p离开作用域,它指向的内存会被自动释放//但如果有其他shared_ptr也指向这块内存,则它就不会被释放:
shared_ptr<Foo> use_factory2(T arg){shared_ptr<Foo>p=factory(arg);//使用preturn p;//当我们返回p时,引用计数进行了递增
}//p离开作用域,它指向的内存不会被释放

因此,如果将shared_ptr存放于一个容器中,而后不再需要全部元素而只使用其中一部分,要记得用erase删除不再需要的那些元素。

程序使用动态内存出于以下原因

  • 程序不知道自己需要使用多少对象 例如:容器类
  • 程序不知道所需对象的准确类型
  • 程序需要在多个对象间共享数据

我们定义一个类,它使用动态内存是为了让多个对象共享相同的底层数据。我们定义一个名为Blob的类,保存一组元素。我们希望Blob对象的不同拷贝之间共享相同的元素,即,当我们拷贝一个Blob时,原Blob对象及其拷贝应该引用相同的底层元素。

Blob类的定义如下:

#ifndef __BLOB__
#define __BLOB__#include<string>
# include<iostream>
# include<vector>
# include<memory>
using namespace std;class Blob {
public:typedef vector<string>::size_type size_type;Blob();Blob(initializer_list<string>il);size_type size()const { return data->size(); }bool empty()const{return data->empty();}//添加和删除元素void push_back(const string &t) { data->push_back(t); }void pop_back();//元素访问string & front();string & back();//获取元素shared_ptr<vector<string>> getData() { return data; }//修改元素void changeData(size_type i,string str) {(*data)[i] = str;}private:shared_ptr<vector<string>>data;void check(size_type i,const string &msg)const;
};Blob::Blob() :data(make_shared<vector<string>>()){}
Blob::Blob(initializer_list<string>il) : data(make_shared<vector<string>>(il)) {}
void Blob::check(size_type i, const string &msg)const {if (i >= data->size())throw out_of_range(msg);
}
string& Blob::front() {check(0,"front on empty Blob");return data->front();
}
string& Blob::back() {check(0, "back on empty Blob");return data->back();
}
void Blob::pop_back() {check(0, "pop_back on empty Blob");data->pop_back();
}
void print(ostream& os,Blob b) {for (auto a : *(b.getData())) {os << a << " ";}os << endl;
}#endif

对Blob进行测试代码如下:

	Blob b1{ "a","an","the" };cout << "b1的元素为:";print(cout,b1);Blob b2(b1);cout << "b2的元素为:";print(cout, b2);b2.changeData(0,"hello");cout << "对b2中的元素进行修改后:" << endl;cout << "b1的元素为:";print(cout, b1);cout << "b2的元素为:";print(cout, b2);

输出结果:

b1的元素为:a an the
b2的元素为:a an the
对b2中的元素进行修改后:
b1的元素为:hello an the
b2的元素为:hello an the

从输出结果我们可以看出,Blob在多个对象间共享数据。当我们拷贝、赋值或销毁一个Blob对象时,它的shared_ptr成员会被拷贝、赋值或销毁。

我们定 义的Blob与vector的区别:

vector<string>v1;
{vector<string>v2={"a","an","the"};v1=v2;//从v2拷贝元素到v1中
}//v2被销毁,其中的元素也被销毁
//v1有三个元素,是原来v2元素的拷贝
Blob<string>b1;
{Blob<string>b2={"a","an","the"};b1=b2;//b1和b2共享相同的元素
}//b2被销毁,其中的元素不能销毁
//b1指向最初由b2创建的元素

直接管理内存

C++语言定义了两个运算符来分配和释放动态内存。运算符new分配内存,delete释放new分配的内存。相对于智能指针,使用这两个运算符管理内存非常容易出错。

使用new动态分配和初始化对象

在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针。
例如:

int *pi = new int(1024);
vector<int> *pv = new vector<int>{1,2,3,4,5};

如果我们提供了一个括号包围的初始化器,就可以使用auto,从此初始化器来推断我们想要分配的对象的类型。只有当括号中仅有单一初始化器时才可以使用auto:

auto p1 = new auto(obj); //p1指向一个与obj类型相同的对象
auto p2 = new auto(a,b,c); //错误:括号中只能有单个初始化器

动态分配的const对象

类似其他任何const对象,一个动态分配的const对象必须进行初始化。

const int *pci = new const int(1024);

内存耗尽 定位new

虽然现代计算机通常都配备大容量内存,但是自由空间被耗尽的情况还是有可能发生。一旦一个程序用光了它所有可用的内存,new表达式就会失败。默认情况下,如果new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常。我们可以改变使用new的方式来阻止它抛出异常。我们称这种形式的new为定位new。

int *p1 = new int;//如果分配失败,new抛出std::bad_alloc
int *p1 = new (nothrow) int;//如果分配失败,new返回一个空指针

释放动态内存

我们通过delete表达式来将动态内存归还给系统。delete表达式接受一个指针,指向我们想要释放的对象:

delete p;

与new类型类似,delete表达式也执行两个动作:销毁给定的指针指向的对象;释放对应的内存。

我们传递给delete的指针必须指向动态分配的内存,或者是一个空指针。释放一块并非new分配的内存,或者将相同的指针值释放多次,其行为是未定义的。

由内置指针(而不是智能指针)管理的动态内存在被显式释放前一直都会存在。

使用new和delete管理动态内存存在三个常见问题

  • 忘记delete内存,会导致“内存泄漏”问题,因为这种内存永远不可能归还给自由空间了。
  • 使用已经释放掉的对象。
  • 同一块内存释放两次。当有两个指针指向相同的动态分配对象时,可能发生这种错误。如果对其中一个指针进行了delete操作,对象的内存就被归还给自由空间了。如果我们随后又delete第二个指针,自由空间就可能被破坏。

delete之后重置指针值

当我们delete一个指针后,指针值就变为无效了。虽然指针已经无效,但在很多机器上指针仍然保存着(已经释放了的)动态内存的地址。在delete之后,指针就变成了人们所说的空悬指针,即,指向一块曾经保存数据对象但现在已经无效的内存的指针。
未初始化指针的所有缺点空悬指针也都有。有一种方法可以避免空悬指针的问题:在指针即将要离开其作用域之前释放掉它所关联的内存。如果我们需要保留指针,可以在delete之后将nullptr赋予指针,这样就清楚地指出指针不指向任何对象。

动态内存的一个基本问题是可能有多个指针指向相同的内存。在delete内存之后重置指针的方法只对这个指针有效,对其他任何仍指向(已释放的)内存的指针是没有作用的,以下代码重置p对q没有任何作用。

int *p(new int(42));
auto q=p;  //p和q指向相同的内存
delete p;//p和q均变为无效
p=nullptr;//指出p不再绑定到任何对象

智能指针和new对比

int *q=new int(42),*r=new int(100);
r=q;
auto q2=make_shared<int>(42),r2=make_shared<int>(100);
r2=q2;

以上代码new指针的两个问题:

  • 内存泄漏问题,r和q都指向42的内存地址,而r原来保存的地址——100的内存再无指针管理,不能进行释放,造成内存泄漏。
  • 空悬指针问题,r和q指向同一个动态对象,如果程序编写不当,很容易产生释放了其中一个指针,而继续使用另一个指针的问题。

智能指针的优势:

  • 将q2赋予r2,赋值操作会将q2指向的对象地址赋予r2,并将r2原来指向的对象的引用计数减1,将q2指向的对象的引用计数加1,这样前者的引用计数变为0,其占用的内存空间会被释放,不会造成内存泄漏。
  • 而后者的引用计数变为2,也不会因为r2和q2之一的销毁而释放它的内存空间,因此也不会造成空悬指针的问题。

shared_ptr和new结合使用

如果我们不出事后一个智能指针,它就会被初始化为一个空指针。我们还可以用new返回的指针来初始化智能指针。接受指针参数的智能指针构造函数是explicit,因此我们不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针。

shared_ptr<int>p1=new int(1024);//错误
shared_ptr<int>p2(new int(1024));//正确:使用了直接初始化形式

定义和改变shared_ptr的其他方法

在这里插入图片描述
在这里插入图片描述

不要混合使用智能指针和普通指针

void process(shared_ptr<int>ptr){}shared_ptr<int> x(new int(1024));//引用计数为1
process(x); //正确,拷贝x会递增它的引用计数,在process中引用计数为2
int j=*x;//正确:引用计数为1
void process(shared_ptr<int>ptr){}int *x(new int(1024));
process(x); //错误,不能将int*转换为shared_ptr<int>
process(shared_ptr<int>(x));//正确,但内存会被释放
int j=*x;//未定义的,x是一个空悬指针

在该调用中,我们将一个临时的shared_ptr传递给process,当这个调用所在的表达式结束时,这个临时对象就被销毁了。销毁这个临时变量会递减引用计数,此时引用计数为0,因此,当临时变量被销毁时,它所指向的内存会被释放,但x继续指向(已经释放的)内存,从而变成一个空悬指针,如果试图使用x的值,其行为是未定义的。
当将一个shared_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr。一旦这样做了,我们就不应该再使用内置指针来访问shared_ptr所指向的内存了。
使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法知道对象何时会被销毁。

不要使用get初始化另一个智能指针或为智能指针赋值

智能指针类型定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象。当我们需要向不能使用智能指针的代码传递一个内置指针时可用get。使用get返回的指针的代码不能delete此指针。

	shared_ptr<int>p(new int(42));//p的引用计数为1int *q = p.get();//正确,但使用q时要注意,不要让它管理的指针被释放,p的引用计数仍然为1{process(shared_ptr<int>(q));}int foo = *p;//错误,此时p的引用计数为1,但p指向的内存已经被释放了,p成为类似空悬指针的shared_ptr

在本例中,p和q指向相同的内存,由于它们是相互独立创建的,因此各自的引用计数都是1。当q所在的程序块结束时,q被销毁,这会导致q所指向的内存被释放。从而q变成一个空悬指针,当我们试图使用p时,将发生未定义的行为。而且当p被销毁时,这块内存会被第二次delete。

智能指针和异常

使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不再需要时将其释放:

void f(){shared_ptr<int>sp(new int(42));//分配一个新对象//这段代码抛出一个异常,且在f中未被捕获
}//在函数结束时shared_ptr自动释放内存

与之相对的,如果使用内置指针管理内存,且在new之后在对应的delete之前发生异常,则内存不会被释放:

void f(){int *ip=new int(42);//动态分配一个新对象//这段代码抛出一个异常,且在f中未被捕获,则内存不会被释放delete ip;//释放内存的代码
}

使用自己定义的释放操作

现有以下类和函数:

struct destination;//表示我们正在连接什么
struct connection;//使用连接所需的信息
connection connect(destination*);//打开连接的函数
void disconnect(connection);//关闭给定的连接

代码一:

void f(destination &d){//获得一个连接connection c = connect(&d);//使用连接//如果我们在f退出前忘记调用disconnect,就无法关闭c了
}

代码二,使用shared_ptr:

//定义删除器:
void end_connection(connection *p){disconnect(*p);}
//创建shared_ptr:
void f(destination &d){//获得一个连接connection c = connect(&d);shared_ptr<connection>p(&c,end_connection);//使用连接//当f退出时(即使是由于异常而退出),connection会被正确关闭
}

当p被销毁时,它不会对自己保存的指针执行delete,而是调用end_connection,接下来end_connection会调用disconnect,从而确保连接被正确关闭。如果f正常退出,那么p的销毁会作为结束处理的一部分。如果发生了异常,p同样会被销毁,从而连接被正确关闭。

使用智能指针基本规范

  • 不适用相同的内置指针值初始化(或reset)多个智能指针
  • 不delete get()返回的指针
  • 不适用get()初始化或reset另一个智能指针
  • 如果使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了
  • 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器

unique_ptr

与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象,与shared相同的操作在表中

unique_ptr特有的操作

在这里插入图片描述
由于一个 unique_ptr拥有它所指向的对象,因此 unique_ptr不支持普通的拷贝或赋值操作:

unique_ptr<string>p1(new string("hello"));
unique_ptr<string>p2(p1);//错误,不支持拷贝
unique_ptr<string>p3;
p3=p1;//错误,不支持赋值

虽然我们不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique_ptr:
在这里插入图片描述

release返回指针

如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责资源的释放:

p2.release();//错误,p2不会释放内存,而且我们丢失了指针
auto p=p2.release();//正确,但我们必须记得 delete p

传递unique_ptr参数和返回unique_ptr

不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr。最常见的例子是从函数返回一个unique_ptr:

unique_ptr<int> clone(int p){return unique_ptr<int>(new int(p));
}

还可以返回一个局部对象的拷贝:

unique_ptr<int> clone(int p){unique_ptr<int>ret(new int(p));//...return ret;
}

向unique_ptr传递删除器

类似shared_ptr,unique_ptr默认情况下用delete释放它所指向的对象。与shared_ptr一样,我们可以重载一个unique_ptr中默认的删除器。
对比shared_ptr的连接程序,我们重写连接程序如下:

//定义删除器:
void end_connection(connection *p){disconnect(*p);}
//创建shared_ptr:
void f(destination &d){//获得一个连接connection c = connect(&d);unique_ptr<connection,decltype(end_connection)*>p(&c,end_connection);//使用连接//当f退出时(即使是由于异常而退出),connection会被正确关闭
}

weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。
在这里插入图片描述
示例代码:

	auto p = make_shared<int>(42);auto q(p);auto p2(p);cout << "p.use_count():" << p.use_count() <<endl;weak_ptr<int>wp(p);cout << "p.use_count():" << p.use_count() << endl;cout << "wp.use_count():" << wp.use_count() << endl;

输出结果:

p.use_count():3
p.use_count():3
wp.use_count():3

由于对象可能不存在,我们不能使用weak_ptr直接访问对象,必须调用lock。次函数检查weak_ptr指向的对象是否仍存在。

if(shared_ptr<int>np=wp.lock()){ //如果np不为空则条件成立
//在if中,np与p共享对象
}

动态数组

new和数组

int *pia = new int[10];//分配10个int,pia指向第一个int

分配一个数组会得到一个元素类型的指针。

我们还可以提供一个元素初始化器的花括号列表:
在这里插入图片描述

释放动态数组

delete p;//p必须指向一个动态分配的对象或为空
delete [] p;//p必须指向一个动态分配的数组或为空,
//数组中的元素按逆序进行销毁,即,最后一个元素首先被销毁,
//然后是倒数第二个,以此类推

智能指针和动态数组

标准库提供了一个可以管理new分配的数组的unique_ptr版本。

unique_ptr<int[]>up(new int[10]);
up.release();//自动用delete[]销毁其指针

指向数组的unique_ptr

在这里插入图片描述
与unique_ptr不同,shared_ptr不直接支持管理动态数组,如果希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器:

shared_ptr<int>sp(new int[10],[](int *p){delete[]p;});
sp.reset();//使用我们提供的lambda释放数组,它使用delete[]释放数组

如果未提供删除器,这段代码将是未定义的。默认情况下,shared_ptr使用delete销毁它指向的对象。

连接两个字符串

代码:

	const char *c1 = "hello";const char *c2 = "world";char *r = new char[strlen(c1)+ strlen(c2)+1];strcpy(r,c1);strcat(r, c2);cout << r << endl;string s1 = "hello";string s2 = "world";strcpy(r,(s1+s2).c_str());cout << r << endl;

输出结果:

helloworld
helloworld

allocator类

new有一些灵活性上的局限,其中一方面表现在它将内存分配和对象构造组合在了一起,这可能会导致不必要的浪费。
标准库allocator类定义在头文件memory中。它帮助我们将内存分配和对象构造分离开来。它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。

标准库allocator类及其算法

在这里插入图片描述

allocator分配未构造的内存

在这里插入图片描述
还未构造对象的情况下就使用原始内存是错误的。
为了使用allocate返回的内存,我们必须用construct构造对象。使用未构造的内存,其行为是未定义的。

当我们用完对象后,必须对每个构造的元素调用destroy来销毁它们。函数destroy接受一个指针,对指向的对象执行析构函数。

allocator算法

在这里插入图片描述
在这里插入图片描述
类似copy,uninitialized_copy返回(递增后的)目的位置迭代器,因此,一次uninitialized_copy调用会返回一个指针,指向最后一个构造的元素之后的位置。

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

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

相关文章

C语言顺序查找二分查找

介绍 顺序查找 按照顺序一个个查找 #include<stdio.h> //顺序查找 int search(int arr[],int len,int aim) {int i;for(i0;i<len;i){if(arr[i]aim){return i;//返回下标 }}return -1;//表示未查询到} int main() {int arr[]{13,355,256,65,234,-1,35,-6,-3,-4,0};…

C++ primer 第12章 12.3 使用标准库:文本查询程序

文章目录使用标准库&#xff1a;文本查询程序文本查询程序设计数据结构在类之间共享数据自己的文本查询程序书中的文本查询程序使用标准库&#xff1a;文本查询程序 我们将实现一个简单的文本查询程序&#xff0c;作为标准库相关内容学习的总结。 我们的程序允许用户在一个给…

C语言二维数组 int arr[2][3]

基础使用 先遍历行再遍历列 #include<stdio.h> //二维数组的基本使用 int main() {//二维数组的初始化int arr1[2][2]{{2,2},{0,0}};int arr2[2][3]{2,2,2,8,8,8};int arr3[6][9];int i,j;for(i0;i<6;i){for(j0;j<9;j){arr3[i][j]1;}}arr3[2][5]0;//打印printf(&…

C++ primer 第13章 拷贝控制

文章目录前言拷贝、赋值与销毁拷贝构造函数合成拷贝构造函数拷贝初始化和直接初始化拷贝初始化的发生&#xff1a;参数和返回值拷贝初始化的限制拷贝赋值运算符重载赋值运算符合成拷贝赋值运算符析构函数析构函数完成的工作什么时候会调用析构函数合成析构函数代码片段调用几次…

C语言 指针自增自减加减运算 p++ p+i

介绍 自增自减代码 #include<stdio.h> #include<string.h> //指针自增--short void increase(short *arr,int len) {int i;arr&arr[0];for(i0;i<len;i){printf("arr[%d]%d,address%p\n",i,*arr,arr);arr;} }//指针自减--char void decrease(char…

C++ 编译与底层

原文链接 编译与底层请你来说一下一个C源文件从文本到可执行文件经历的过程&#xff1f; 对于C源文件&#xff0c;从文本到可执行文件一般需要四个过程&#xff1a;预处理阶段&#xff1a;对源代码文件中文件包含关系&#xff08;头文件&#xff09;、预编译语句&#xff08;…

C语言 指针数组-字符指针数组整型指针数组 char*s[3] int*a[5] 数组指针int(*p)[4]

基本介绍 1.指针数组:由n个指向整型元素的指针而组成,里面存放指针 Int *ptr[3]; 2.地址: ptr[i]:元素地址 &ptr[i]:指针地址 图示 代码: 内存布局: 代码 #include<stdio.h> #include<string.h> //指针数组--int void pointer(int *arr,int len) {int …

C语言 多重指针--整型字符字符串 int**pp

介绍 多重指针:一个指针指向另一个指针 离值越近的指针级别越大:一级 内存布局 代码 图示: 多重指针–整型 #include<stdio.h> #include<string.h> //多重指针--整型//二级指针 void two() {printf("二级指针:\n");int a896;int *p&a,**pp&…

C++ primer 第13章 拷贝控制

文章目录前言拷贝、赋值与销毁拷贝构造函数合成拷贝构造函数拷贝初始化和直接初始化拷贝初始化的发生&#xff1a;参数和返回值拷贝初始化的限制拷贝赋值运算符重载赋值运算符合成拷贝赋值运算符析构函数析构函数完成的工作什么时候会调用析构函数合成析构函数代码片段调用几次…

牛客网C++面经 C++11

请问C11有哪些新特性&#xff1f; auto关键字&#xff1a;编译器可以根据初始值自动推导出类型。但是不能用于函数传参以及数组类型的推导nullptr关键字&#xff1a;nullptr是一种特殊类型的字面值&#xff0c;它可以被转换成任意其它的指针类型&#xff1b;而NULL一般被宏定义…

C语言 返回指针的函数--指针函数 int* max(int a)

定义 strlong示例代码 代码1: #include<stdio.h> #include<string.h> //返回指针的函数//比较两个字符串,返回更长的字符串 char *strlong(char* a,char* b) {char *p1&a[0];char *p2&b[0];while(true){if(*p1\0){return b;}else if(*p2\0){return a;}p1…

第2、3讲 图像的存储格式

本图像处理系列笔记是基于B站杨淑莹老师的课程进行学习整理的。 文章目录黑白图像8位灰度索引图像8位伪彩色索引图像24位真彩色图像图像文件格式BMP文件存储格式BMP文件头位图信息头颜色表位图信息——BITMAPINFO结构BMP位图文件汇总按照颜色深度分类&#xff0c;常用图像文件&…

Ubuntu18.04.4 环境下对属性加密算法CP-ABE环境搭建

注意事项 cpabe依赖pbc&#xff0c;pbc依赖gmp&#xff0c;gmp依赖M4、bison、flex如果权限不够 &#xff0c;命令的前面加上sudo &#xff0c;不要直接使用root用户进行操作&#xff0c;其带来的隐患有很多 第一步 配置简单的环境 简单环境 包括gcc、g、make、cmake、openss…

C语言 函数指针 int(*ptr)(int,int)

基本介绍 函数指针:指向函数的指针 与数组类似 定义 Int(*pmax)(int ,int)max; Int(*pmax)(int x,int y)max;//形参名称不重要 函数返回类型(*指针)(形参类型)函数名称; 具体案例 代码: *pmax取到函数本身 调用函数指针方式: (*pmax)(x,y); pmax(x,y);//与java中调用函数一…

C++ primer 第14章 操作重载与类型转换

文章目录基本概念直接调用一个重载的运算符函数某些运算符不应该被重载使用与内置类型一致的含义选择作为成员或者非成员输入和输出运算符重载输出运算符<<输出运算符尽量减少格式化操作输入输出运算符必须是非成员函数重载输入运算符>>算术和关系运算符相等运算符…

C语言 回调函数 produce(arr,len,getRand)

基本介绍 回调函数:形参中包含另一个函数的函数指针 用函数指针接收另一个函数 案例 代码解析 具体代码 #include<stdio.h> #include<stdlib.h> //回调函数--//函数原型 int getRand(); int *produce(int*arr,int len,int(*get)()); int main() {int arr[10…

C语言 动态内存分配机制(堆区) int*p=malloc(5*sizeof(4))

C程序内存分配图 栈区:局部变量 堆区:动态分配的数据 静态存储区/全局区:全局变量,静态数据 代码区:代码,指令 内存分配说明 内存动态分配的相关函数 堆区: #inlcude<stdlib.h> Malloc(size);//分配长度为size个字节的连续空间 Calloc(n,size);//分配size个长度为n…

C语言 结构体 struct Cat cat1;

引入 使用传统技术解决 需要定义多个变量或数组 结构体与结构体变量的关系示意图 类似Java类中的对象(结构体)与属性(结构体变量) 一切物体都可以看作对象(结构体) 补充:C语言数据类型 简单使用案例 代码 Cat是我们自己定义的数据类型 struct Cat cat1;//创建struct Cat的…

C语言 共用体/联合体 union

引入 传统技术的缺陷—结构体 共用体基本介绍 共用体与结构体一样都是值传递 定义共用体的三种方式 内存布局 共用体数据空间占用最大的成员的数据空间大小 案例解析 1) 2) 3) 4) 注: 1010 1101 0101 0100所对应的十进制是负数 计算机中的二进制都是以补码存储的,所…

C语言 项目练习-家庭收支软件

目标 需求说明 界面说明 登记收入界面: 登记支出界面 收支明细界面 退出界面 项目代码改进要求 自己完成的代码 版本1 #include<stdio.h> #include<string.h> void choose(int button,int i); //项目--家庭收支软件 static double total10000;//总金额 #de…