- C++ 第三篇
- 一、内存管理
- 二、引用
一 、内存管理
二 、引用
一、内存管理
内存管理
- 分配 : new, new[]
- 释放: delete, delete[]
#include <iostream>
using namespace std;
int main(void)
{
// int *pi =(int*)malloc(sizeof(int)); // c 语言中的形式
int *pi = new int;
*pi = 123;
cout << *pi <<endl;
delete pi;
pi = NULL;
// init pi2 when new
int *pi2 = new int(123);
// *pi2++; // error pi2先++ ,非法访问后面的内存
(*pi2)++; //
cout << *pi2 <<endl;
delete pi2; // 如果上面非法访问后面的内存,这里会出现error,相当没有new 那一块内存,但是删除了
pi2 = NULL;
}
c++ 中数组的分配和释放
#include <iostream>
using namespace std;
int main(void)
{
int * parry = new int[10];
for(int i=0; i< 10; i++){
parry[i] = i;
cout << parry[i] << endl;
}
delete[] parry;
parry = NULL;
// c++ 11
int * parry2 = new int[10]{1,2,3,4,5,6,7,8,9,10};
for (int i=0; i< 10; i++)
{
cout << parry2[i] << endl;
}
delete[] parry2;
parry2 = NULL;
}
指针使用的错误 实例
#include <iostream>
using namespace std;
int main()
{
int * pi;
delete pi;// Delete wild pointer dangerous! 释放野指针 很危险,可能出错
int *pi2 = NULL;
delete pi2; // 可以释放空指针,但是没有任何的意义 OK but no means
int *pi3 = new int;
delete pi3;
delete pi3; // 连续两次释放一块内存,会出错
}
二 C ++ 的引用
2.1 定义
引用即别名,就是某个变量的别名,对引用的操作和对变本身的操作完全相同
注意: 引用在定义式必须初始化,初始化以后绑定其他变量名,不能再修改。
注意: 引用类型和绑定的变量类型必须要一致
2.2 语法:
类型 & 引用名 = 变量;
int a = 100;
int & b = a; // b 是a 的别名
b++;
cout << a<< endl; // 相当于执行a++
char & r = a; // 错误 类型不一致
2.3 常引用
2.3.1
定义引用时加上const修饰,即为常引用,不能通过引用修改引用的目标变量。
const 类型 & 引用名 = 变量;
类型 const & 引用名 = 变量; // 和上面完全相同
int a = 100;
int &b = a;
a++; // ok
b++; // error
2.3.2
普通引用也叫做左值引用,只能引用左值。而常引用也叫做万能引用,既可以引用左值,也可以引用右值;
using namespace std;
int func(void)
{
int num = 100;
cout << "&num = "<< &num<<endl;
return num; //
}
int main()
{
int a = 123;
int &b = a; //
int &c = 100; // error
const int &d = 200;
// 1. 首先将a 转化为char,结果保存在临时变量中
2. e实际引用的是临时变量,临时变量是右值
3. 普通引用只能引用左值,因此出现错误
char& e = a; // error
const char &f =a; // ok // 使用万能引用可以引用右值,因此正确
int i = func(); // 函数调用,返回的结果也是一个临时变量,临时变量是右值
const int &r3 = func(); // 因此引用需要加上const
cout << "&r3 =" << &r3<< endl; // 可以看到程序运行后,r3 的地址和num 的地址不一样
return 0;
}
2.3.3
关于左值和右值
左值(lvaule) : 能放在赋值表达式左侧,可以被修改
右值(rvalue) : 只能放在赋值表达式右侧,不能被修改
// 一下这些是左值
int a =100
int b = 200;
a+= b = 300 //
++a // 左值 相当于 a = a+1;
// 以下这些是右值
(a + b) // 因为a+ b 的结果放在了临时变量中
a ++ // 右值
cout << a++ <<endl; // 这个是100
cout << a << endl; // 这个是101
a ++ 先算的是++ ,但是该表达式表示 先将a 的值存入临时变量,然后++ 操作赋值给a. 因此 a++ 返回的其实是a 的临时变量的值, a本身完成了++ 操作
2.4 引用型函数参数
- 可以将引用用于函数参数,这是实参就是形参的别名,可以通过形参直接修改实参的值,同时还可以避免参数传值的开销,提高效率。
- 引用型的参数有可能意外的修改实参的值,如果不希望修改实参本身,可以将形参定义为常引用,提高传参效率的同时,还可以接收常量实参。
#include <iostream>
using namespace std;
void swap1 (int &x, int &y)
{
x = x^y;
y= x^y;
x = x^y;
}
void swap (int *x, int *y)
{
*x = *x^*y;
*y= *x^*y;
*x = *x^*y;
}
int main(void)
{
int a = 10;
int b = 20;
swap(&a,&b);
cout << "a ="<< a<<" "<< "b=" << b<< endl;
swap1(a,b);
cout << "a ="<< a<<" "<< "b=" << b<< endl;
}
结构体传参数引用
#include <iostream>
using namespace std;
struct Teacher {
char name[100];
int age;
double salary;
};
void print(const struct Teacher& teacher)
{
cout << teacher.name<< teacher.age/*++*/<< teacher.salary<<endl;
}
int main()
{
const struct Teacher t1 = {"xiaoming",32,8000.3}; // 如果变量是常类型变量,那么引用必须是常引用
print(t1) ;
return 0;
}
2.5 引用型函数返回值
- 可以将函数的返回值声明为引用,这时函数的返回值结果就是return 后面的数据的别名,可以避免返回值所带来的开销,提高代码的传参效率。
- 如果返回类型是左值引用,那么该函数调用表达式的结果也是一个左值
- 不能返回局部变量的引用,因为所引用的内存会在函数释返回后释放,使用危险!!! 但是可以从函数中返回成员变量,静态变量,全局变量的引用
#include <iostream>
using namespace std;
struct Teacher {
int age;
int& get_age()
{
int num = 100;
return num; // 这是很危险的,返回的局部变量会被销毁,则引用相当于不知道引用了什么。。。,结果不可预知
//return age;
}
};
int main()
{
struct Teacher t1 = {20};
t1.get_age() = 30;
cout << t1.age<< endl;
return 0;
}
2.6 引用和指针
- 从C语言的角度看,引用的底层实现是指针。但是C++推荐使用引用,不推荐使用指针。
int i = 100;
int & ri= i;
int * const pi = i;
ri 与 *pi 是等价的
底层的汇编代码是等价的。 - 指针可以不做初始化,指向的目标变量可以在初始化以后改变。
引用必须初始化,而且一旦初始化,其目标将不会改变。
int a = 10, b = 20;
int *p ;
p = &a;
p = &b;
int & r // error
int & r = a;
r = b ; // 仅仅是一个赋值操作
3.1 可以定义指针的指针(二级指针),但是没有引用的指针
3.2 可以定义指针的引用(指针的别名),但是没有引用的引用
3.3 可以定义指针数组,但是不能定义引用数组
但是可以定义数组引用(数组的别名)
3.4 可以定义指针数组。但是不可以定义引用数组。但是也可以定义数组引用(数组别名)
3.5 可以定义函数的指针,也可以定义函数的引用,语法特定一致
#include <iostream>
using namespace std;
int func(int i, int j)
{
return (i+j);
}
int main()
{
int a = 100;
int *p = &a;
int **pp = &p;
int &r = a;
int & * rp = r; // error 没有引用的指针,因为引用没有 地址,定义引用的指针也就没有意义
int * & pr = p; // 可以有指针的引用
int && rr = r; // error 没有引用的引用
int i= 10, j= 20, k = 30;
int *parr[3] = {&i,&j,&k}; // ok
// int & rarr[3] ={i,k,k}; // error 没有引用数组
int arr[3] ={i,j,k};
int (&rarr)[3] = arr; // ok 可以有数组的引用,相当于数组的别名
int (*pfunc)(int, int) = func;
pfunc(i,j);
int (&rfunc)(int, int) = func;
rfunc(j,k);
return 0;
}
三 类型转换
- 隐式类型转换
char c = 'a';
int i = c;
-------------------------------------
void func(int i) {...}
func (c)
--------------------------------------
int func (void)
{
char c = 'a';
return c;
}
- 显式类型转换
c++ 兼容C 中的隐式类型转换
2.1 强制转换
char c = ‘a’;
int i = (int) c; // c风格
int i = int©; // c++ 风格
2.2 c++ 拓展了四种操作符形式的类型转换
2.2.1.- 静态类型转换 static_cast
语法: 目标类型变量= static_cast<目标类型>(源类型变量)
适用场景: 将void * 的指针转换为其他类型的指针。
使用static_cast 更加安全,如果将整形指针转化为整形,就会失败。
- 静态类型转换 static_cast
#include <iostream>
using namespace std;
int main()
{
int *pi =NULL;
// static cast
void * pv = pi;
pi =static_cast<int *> (pv);
int i = static_cast<int>(pi); // 此处不合理,因此编译器报错
return 0;
}
2. 动态类型转换dynamic_cast
语法: 目标类型变量= dynamic_cast<目标类型>(源类型变量) 主要用于多态,父子类型转换
3. (去)常类型转换 : const_cast
语法: 目标类型变量= const_cast<目标类型>(源类型变量) 主要用于去除指针或者变量的常属性
#include <iostream>
using namespace std;
int main()
{
const int ci = 100;
int *pi = &ci; // error 在c++ 中,使用直接将const int 转换为int 是错误的,相当于扩大了范围
int* pi = const_cast<int*> (&ci); // 需要去常转换
return 0;
}
#include <iostream>
using namespace std;
int main()
{
const int ci = 100;
int* pi = const_cast<int*> (&ci); // error
*pi = 200;
cout << "ci = "<< ci << endl;
cout<< "*pi = "<< *pi << endl;
return 0;
}
这段代码的输出结果是
ci = 100
*pi = 200
4. 重解释类型转换: reinterpret_cast