C++2节:C++动态内存分配,引用,四种类型转换

C++对内存的控制和管理严格到令人发指的地步,所以关于动态内存,关于new/delete运算符,一定要十分熟悉!C++中的new/delete和C中malloc/free区别也要明白。


C++的动态内存分配


1 回顾C语言中动态内存分配
1)分配:malloc()/calloc()/realloc()
2)释放:free()
3)错误处理:返回值

2 C++使用new/delete运算符分配内存
1)分配:new/new[]
2)释放:delete/delete[]
3)错误处理:异常(后面讲)

*注:带 [ ] 表示对数组类型的内存进行操作

/*
    注释为C语言的动态分配内容方式
*/
 //int* p = (int*)malloc(sizeof(int));
  int* p = new int;
  *p = 100;
  //free(p);
  delete p;
  ---------------------------------------
  //int* p = (int*)malloc(sizeof(int));
  //*p = 0;
  int* p = new int(0);//分配内存同时初始化
  *p = 100;
  //free(p)
  delete p;
  =======================================
  //int* pa = (int*)malloc(sizeof(int)*10);
  int* pa = new int[10];
  pa[2] = 123;
  cout << *(p+2) << endl; //输出123,此时+2,加的是两个int
  //free(pa);
  delete[] pa;

**拓展:new的用法

根据new的定义,new有三种用法,

1  void* operator new (std::size_t size) throw (std::bad_alloc);  

这种用法申请内存,失败时抛出bac_alloc异常

如 int p = new int;

Class A;

A* p = new A;//申请内存并初始化ClassA, 调用A的构造函数
2 void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw();  

这种用法申请内存,失败时返回空NULL

A* a = new(std::nothrow) A;
3 void* operator new (std::size_t size, void* ptr) throw();  

这种用法是placement new,用于在给定的内存空间中初始化变量或对象  

char buf[100];

A* p = new(buf)A;//在buf上初始化Class A,调用A的构造函数

C++的引用(Reference)


1 定义
1)引用就是某个变量别名,对引用的操作与对该变量的操作相同。
2)语法:
   类型& 引用名(别名) = 变量;
   注:引用在定义时要绑定一个变量,初始化以后不能修改引用的目标。
   注:引用的类型与绑定变量类型要一致。
eg:

   int a = 100;
   int& b = a;//b就是a的别名
   b++;//等价于a++;
   cout << a << endl;//101
   
   int c = 200;
   b = c;//将c的值赋值给b,等价于给a赋值
   cout << a << endl;//200

2 常引用
1)定义引用时加const修饰,即为常引用,不能通过常引用修改引用的目标。
2)语法:
  const 类型& 引用名(别名) = 变量/右值;
eg:
int a = 0;
const int& b = a;//b就是a的常引用。
b = 10;//error
注:普通的引用只能引用左值,而常引用既可以引用左值也可以引用右值,所以也可以把常引用称为万能引用。
eg:
int& r = 100;//error
const int& r = 100;//ok
**注:关于左值和右值
1)左值:可以放在赋值运算符左侧,存取数据
-->普通的变量都是左值
-->前++/--表达式结果是左值
   int num = 0;
   ++num = 10;//ok
-->赋值表达式结果是左值

   int a = 0;
   int b = 10;
   (a = b) = 20;
   cout << a << ',' << b << endl;//20,10

2)右值:只能放在赋值运算符右侧
-->字面值常量都是右值
-->大多数表达式结果都是右值
   eg:后++/-- 算符运算 逻辑运算 ...
   int num = 0;
   num++ = 10;//error 
   
   int a = 3,b =5;
   (a + b) = 20;//error

3 引用型函数参数
1)将引用用于函数的参数,可以直接修改实参变量的值,同时减小函数调用的开销。
2)引用参数有可能意外修改实参变量的值,如果不希望修改实参本身,可以将形参定义为常引用,提高传参效率的同时还可以接收常量型的实参。

#include <iostream>
using namespace std;
struct Student{
    char name[128];
    int age;
};
void print(const Student& s){
    cout << s.name << ',' << s.age/*++*/ << endl;
}
int main(void)
{   
    const Student student = {"张飞",25};
    print(student);
    print(student);
    return 0;
}

4 引用型函数返回值
1)可以将函数的返回值声明为引用,避免函数返回值所带来的内存开销。
2)如果一个返回类型被声明为引用,那么该函数返回值可能是一个左值。
3)为了避免在函数外部修改引用的目标变量,可以返回常引用。

注:不要从函数中返回局部变量的引用,因为所引用的目标变量内存会在函数调用结束以后被释放,可以返回成员变量、静态变量、全局变量的引用。

#include <iostream>
using namespace std;

struct A{
    int data;
    /*const*/ int& foo(void){
        return data;
    }
    //返回局部变量引用,危险!
    int& bar(void){
        int a = 100;
        return a;
    }
};
int main(void)
{
    A a = {0};
    cout << a.data << endl;//0
    //a.data = 100
    a.foo() = 100;
    cout << a.data << endl;//100
    return 0;
}



 

5 引用和指针:

可以看到,引用就像指针一样,减少内存开销,提升程序效率,而且可读性更好,那指针和引用有什么区别呢?
1)引用的本质就是指针
  int i = 100;
  int& ri = i;
  int* const pi = &i;
  ri《==》*pi
2)指针可以不做初始化,其指向的目标也可以在初始化以后再修改(指针常量例外);而引用必须初始化,一旦初始化其引用目标不能再做修改。
eg:
  int a=3,b=5;
  int* p;//ok
  p = &a;//p指向a
  p = &b;//p指向b
  -------------------
  int& r;//error
  int& r = a;
  r = b;//不是修改引用目标而是将b值赋值给a
  ====================
3)可以定义指针的指针(二级指针),但是不能定义引用的指针。
eg:
  int a = 100;
  int* p = &a;
  int** pp = &p;//ok,二级指针
  -----------
  int& r = a;
  int&* pr = &r;//error
4)可以定义指针的引用(指针的别名),但不能定义引用的引用。
eg:
  int a = 100;
  int* p = a;
  int*& rp = p;//ok
  ---------------
  int& r = a;
  int&& rr = r;//error,//在C++11中称为右值引用
  
5)可以定义指针数组,但是不能定义引用数组
int a=1,b=2,c=3; 
int* parr[3] = {&a,&b,&c}; //ok
int& rarr[3] = {a,b,c};//error

6)可以定义数组的引用(数组的别名)
int arr[3] = {1,2,3};
int (&rarr)[3] = arr;//ok
 
7)和函数指针一样,也可以定义函数的引用,其语法特性和函数指针一致。
eg:

  void func(int a,double d){}
  int main(void){
     //函数指针
     void (*pfunc)(int,double) = func;    
     pfunc(10,3.14);
     //函数的引用(函数的别名)
     void (&rfunc)(int,double) = func;    
     rfunc(10,3.14);
  }

类型转换


1 隐式类型转换
eg:
  char c = 'A';  
  int i = c;//隐式
  
  void foo(int i){}
  foo(c);//隐式
  
  int fun(void){
    char c = 'A';
    return c;//隐式
  }
2 C++兼容C语言显式类型转换(强制类型转换)
 eg:
  int* p = (int*)malloc(4); //强制类型转换
 eg:
  char c = 'A';  
  int i = (int)c;//强制类型转换
  int i = int(c);//C++风格的强制转换

3 C++增加了四种操作符形式显式类型转换
1)静态类型转换
语法:
  目标类型变量=static_cast<目标类型>(源类型变量);
适用场景:主要用于将void*转换为其它的指针
eg:
  int a = 100;
  void* pv = &a;
  //int* pi = pv;//不能隐式转换,只能显式转换
  int* pi = static_cast<int*>(pv);
  
2)动态类型转换(初学者可先跳过这条)
语法:
 目标类型变量=dynamic_cast<目标类型>(源类型变量);

适用场景:将基类的指针或引用安全地转换成派生类的指针或引用,并用派生类的指针或引用调用非虚函数。前提是该基类至少含有一个虚函数(比如虚析构函数)。

class Base{
    Base();
    virtual ~Base();//至少有个一个虚函数
};
class Child : public Base{
    Child();
    ~Child();
};
int main(){
    Base* pb = new Child;
    Chile* pc = dynamic_cast<Chile*>(pb);
    return 0;
}

3)常类型转换
语法:
 目标类型变量=const_cast<目标类型>(源类型变量);
适用场景:
 主要用于去除一个指针或引用的常属性。
eg:
  const int a = 10;
  const int* pa = &a;
  *pa = 20;//error
  int* p2 = const_cast<int*>(pa);
  *p2 = 20;//ok

4)重解释类型转换
语法:
  目标类型变量 = reinterpret_cast<目标类型>(源类型变量);
适用场景:
  --》在指针和整形数之间的转换
  --》任意类型的指针或引用之间转换
eg:物理内存0x12345678存放一个数据100;
  int* p = reinterpret_cast(int*)(0x12345678);
  *p = 100;
  

C++之父本贾尼·斯特劳斯特卢普给C程序建议
1)慎用宏,用const、enum和inline替换
#define PAI 3.14
  ==》const double PAI = 3.14;

#define STATE_SLEEP 0
#define STATE_RUN   1
#define STATE_STOP  2
  ==》enum STATE{SLEEP,RUN,STOP};
  
#define max(a,b) ((a)>(b)?(a):(b))
  ==》inline int max(int a,int b){
        return a > b ? a : b;
      }
2)变量随用随声明同时初始化 
3)尽量适用new和delete少用malloc和free
4)少用void*、指针计算 
5)少用联合体、强制转换 
6)尽量同string类型表示字符串,少用C风格char*

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值