C++学习(五)类和动态内存分配

1.(new和delete)VS(智能指针)

对象的生命周期:
全局对象:程序启动时分配,程序结束时销毁
局部自动对象:进入块时分配,离开块时销毁
局部static对象:第一次使用时分配,程序结束时销毁

c++中使用new和delete运算符来动态控制内存。

StringBad::StringBad(const char *s)
{

 len=std::strlen(s);
 str=new char[len+1];
 std::strcpy(str,s);
 num_string++;
 cout<<num_string<<":\" "<<str<<"\"object created\n";
}
类成员str是一个指针,因此使用构造函数必须提供内存来存储字符串。
StringBad::~StringBad()
{
	cout<<"\""<<str<<"\"object deleted,";
	--num_strings;
	cout<<num_string<<"left\n";
	delete[] str;
}
当StringBad对象过期时,str指针也将过期。但str影响的内存仍然被分配,除非使用delete将其释放。注意:删除对象可以释放的对象本身占用的内存,但并不能自动的释放属于对象成员的指针指向的内存,因此,需使用析构函数。

new和delete缺点:

  1. 如果忘记delete,会引发内存泄漏

  2. 如果在还有指针引用内存的情况下delete,使用指针时就会产生非法内存的错误
    因此,新的标准库提供了智能指针来帮助程序员管理动态内存

    • shared_ptr
    • unique_ptr
    • weak_ptr
      它们都是模板类,定义在memory头文件中
      shared_ptr类
      默认初始化的智能指针中保存一个空指针:

    shared_ptr p1;
    shared_ptr<list> p2;
    make_shared 函数使用传递参数来构造给定类型的对象

    shared_ptr p3=make_shared(42);
    shared_ptr p4=make_shared(10,‘9’);
    shared_ptr p5 = make_shared();//不传递任何参数,对象会进行值初始化。

1.1 为什么使用智能指针

可以认为每个shared_ptr对象都有一个关联的计数器,通常称作引用计数器。

  1. 当拷贝一个shared_ptr,计数器就会增加,如
    (1)一个shar_ptr初始化另一个shared_ptr
    (2)作为函数参数
    (3)作为函数返回值
  2. 当我们给shared_ptr赋一个新值或者shared_ptr被销毁时,计数器会递减。
  3. 一旦一个shared_ptr的技术器变成0,它就会自动释放自己所管理的对象。
shared_ptr<int> p=make_shared<int>(10);

make_shared函数在堆上开辟一块动态内存,并将指向该内存的指针包装成智能指针返回给p,这块内存的引用计数为1。(引用计数是对内存而言的)

p是一个局部自动对象,当程序离开块时,p被销毁,引用计数减1。引用计数为0,动态内存被释放。

unique_ptr

一个unique_ptr “独享”它所指向的对象

当unique_ptr被销毁时,它所指向的对象也被销毁

初始化unique_ptr必须采用直接初始化形式

unique_ptr<double> p1;  
unique_ptr<int> p2(new int(42));

weak_ptr

weak_ptr指向由一个shared_ptr管理的对象,但是不会影响它的引用计数

当创建一个weak_ptr时,要用一个shraed_ptr来初始化它

auto p = make_shared<int>(42);  
weak_ptr<int> wp(p);

由于weak_ptr不影响引用计数,它指向的对象可能不存在,所以不能直接使用weak_ptr访问对象

一种安全的做法是使用lock函数,如果weak_ptr指向的对象存在,lock函数返回一个指向共享对象的 shared_ptr

if(shared_ptr<int> np = wp.lock())  
{  

}

2. 复制构造函数

(1)复制构造函数即将一个对象复制到新创建的对象中。用于初始化过程中。
原型:

Class_name (const  Class_name &);
如上的
StringBad(const StringBad &);

(2)何时调用复制构造函数?
新建一个对象并将其初始化同类现有的对象时,复制构造函数否将被调用。
假设motto是一个StringBad对象,则下面的4种声明都调用了复制构造函数。

	StringBad ditto(motto);
	StringBad ditto = motto;
	StringBad ditto = StringBad(motto);//使用复制构造函数生成临时对象,然后进行赋值。
	StringBad *pStringBad = new StringBad(motto);//声明使用motto初始化一个匿名对象,并将新对象的地址赋给pString指针。

3. 深拷贝vs浅拷贝

浅拷贝:直接为数据成员赋值(将值保存在相应的空间里)

#include <iostream>
#include <cstring>
using namespace std;
class Test
{
	private:
	 int a;
    char *str;
public:
Test(int b, char *s)
{
    a=b;
    strcpy(str,s);  //肇事地点,但不是祸端
}
Test(const Test& C)
{
    a=C.a;
    strcpy(str,C.str);//肇事地点
}
void show ()
{
    cout<<a<<","<<str<<endl;
}
};

int main()
{
Test a(100,"hello");//调用,将hello复制给str,此时的str未经分配地址,是“野指针”
Test b(a);
a.show();
b.show();
return 0;
}

深拷贝:在构造函数中,为指针类型的成员,分配专门的空间,以这条规则构造的复制,称为深复制。

#include <iostream>
#include <cstring>
using namespace std;
class Test
{
	private:
	 int a;
    char *str;
public:
Test(int b, char *s)
{
    a=b;
    str=new char[strlen(s)+1];
    strcpy(str,s);  //上一条语句解决肇事地点
}
Test(const Test& C)
{
    a=C.a;
    str= new char[strlen(s)+1];
    strcpy(str,C.str);//解决了肇事地点
}
~Test()
{
delete []str;

}
void show ()
{
    cout<<a<<","<<str<<endl;
}
};

int main()
{
Test a(100,"hello");//调用,将hello复制给str,此时的str未经分配地址,是“野指针”
Test b(a);
a.show();
b.show();
return 0;
}

4. static

转载自https://www.cnblogs.com/jhmu063/p/7131997.html
static的作用主要有两种:

第一个作用是限定作用域;第二个作用是保持变量内容持久化;

c语言中static的用法:

1、全局静态变量:

用法:在全局变量前加上关键字static,全局变量就定义成一个全局静态变量。 static int temp;

内存中的位置:静态存储区,在整个程序运行期间一直存在。

初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);

作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。

2、局部静态变量:

在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。

内存中的位置:静态存储区

初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);

作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;

3、静态函数:

在函数返回类型前加关键字static,函数就定义成静态函数。函数的定义和生命在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用;

c++中static的用法:

1、类的静态成员:

class A{
	private:
	static int val;
	 };

在cpp中必须对他进行初始化,初始化时使用作用域运算符来标明他所属类,其属于该类的所有成员共有,只有一个拷贝;

2、类的静态成员函数:

 class A{

	private:
	static int func(int x);
 };

实现的时候也不需要static的修饰,因为static是声明性关键字;类的静态函数是该类的范畴内的全局函数,不能访问类的私有成员,只能访问类的静态成员,不需要类的实例即可调用;实际上,他就是增加了类的访问权限的全局函数;

void  A::func(int);

静态成员函数可以继承和覆盖,但无法是虚函数;

3、只在cpp内有效的全局变量:

在cpp文件的全局范围内声明:

static int val = 0;

这个变量的含义是该cpp内有效,但是其他的cpp文件不能访问这个变量;如果有两个cpp文件声明了同名的全局静态变量,那么他们实际上是独立的两个变量;

4、只在cpp内有效的全局函数:

函数的实现使用static修饰,那么这个函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突;

warning:不要再头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++的stringC++标准库中提供的一个用于处理字符串的。它提供了一系列成员函数和操作符重载,使得字符串的操作更加方便和高效。 string的特点包括: 1. 动态内存管理:string会自动管理字符串的内存,无需手动分配和释放内存。 2. 可变性:string对象可以随时修改其内容,包括插入、删除、替换等操作。 3. 字符串操作:string提供了丰富的字符串操作函数,如查找、比较、连接、截取等。 以下是一些常用的string成员函数和操作符重载: 1. 构造函数:可以使用不同的方式创建string对象,如默认构造函数、拷贝构造函数、从C风格字符串构造等。 2. 赋值操作:可以使用赋值运算符=将一个string对象赋值给另一个对象。 3. 连接操作:可以使用+运算符将两个string对象连接起来。 4. 访问字符:可以使用下标运算符[]或at()函数来访问字符串中的单个字符。 5. 获取长度:可以使用length()或size()函数获取字符串的长度。 6. 查找子串:可以使用find()函数在字符串中查找指定的子串。 7. 插入和删除:可以使用insert()函数在指定位置插入字符或子串,使用erase()函数删除指定位置的字符或子串。 8. 截取子串:可以使用substr()函数截取指定位置和长度的子串。 9. 比较字符串:可以使用比较运算符==、!=、<、>等来比较两个字符串的大小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值