new & delete
在声明一个新的之神变量的时候,最好需要用 new
来分配一块合适的内存,并且在使用结束之后用 delete
进行删除。在cpp中,对自定义的数据类型使用 new
和 delete
,会分别调用解析函数和析构函数。
应当尽量避免未分配内存直接声明指针变量
// 声明和释放指针变量
int* a_pt = new int;
delete a_pt;
动态数组
// 用new创建动态数组
int* psome = new int [10];
delete [] psome;
动态数组的使用方式,其实基本等同于正常的数组
*psome; // 数组的第一个元素
psome[i]; //数组的第i个元素
// 指针允许进行加减操作,对应的是移动相应的变量类型位置
psome += 1; // 此时psome指向数组中第一个元素的位置
!注, 对于一般的数组声明,其变量名(my_array
)也等价于 &my_array[0]
。但是数组的便俩革命不允许进行加减操作
int my_array[10]={0};
std::cout << my_array << std::endl;
// 0x61fd70
my_array + 1; // not valid
指向对象(结构体)的指针
该类指针指向对象的首地址,引用该类内部成员的话,使用 ->
运算符。
struct person
{
std::string name{"Haymax"};
int age=18;
};
person *me = new person {"Haymax", 21};
std::cout << me->age;
std::cout << (*me).age;
delete me;
数组的动态联编和静态联编
在声明的时候,使用形如 int array[10] {0};
进行定义的数组使用的是静态联编,在编译的时候就分配了内存。而使用 int *array = new int [10]
,是在运行过程中动态进行内存分配和回收的,因此必须加上 delete
释放内存。
指针常量和常量指针
指针常量——指针类型的常量
int *const a;
代表的是变量 a
是一个常量,a
的类型是一个 int指针
。也就是说 a
的值(它指向的地址)是不可以进行更改的,但是该地址存储的值是可以更改的。
int *const a;
int *b = new int;
*a = 10; //合法
a = b; //非法
常量指针——指向常量的指针
const int *a;
int const *a;
常量指针说的是,该指针变量指向的是一个常量。也就是说,指针所指向的地址是可以改变的,但是地址中的值是常量不可改变。常用于函数参数传递,尤其是数组。
使用 const
之后,函数参数传入之后会自动生成一个副本。
int sum_arr(const int *begin, const int *end);
int sum_arr(const int *begin, const int *end) {
const int *ptr;
int sum=0;
for (ptr=begin; ptr!=end; ptr++){
sum += *ptr;
}
return sum;
}
int cookies[5] = {1, 2, 3, 4, 5};
int all_cookies = sum_arr(cookies, cookies+5);
在上述代码中,传入的函数中的值是不可更改的常量。也即对 *ptr
进行赋值是非法的。
上述代码中,声明 ptr
的时候的 const
关键字是必须的,因为在定义函数的时候,用的是 const int *begin
,必须对应。否则在 ptr=begin
的赋值操作会报错。
总结:
事实上,可以将普通变量的地址给到 const 指针,只不过此时,对于该指针而言,它指向的地址中的值是不可更改的。也可以将 const 变量的地址给 const 指针。
但是,将 const 变量的地址给普通指针是不可行的,在编译的时候会报错,比如下面的例子:
const int a = 0;
int *ptr = &a; // 非法
二维数组传参
int data[3][4] = {
{1, 2, 3, 4},
{2, 3, 4, 5},
{3, 4, 5, 6}
};
// 正确的函数原型
int sum(int arr[][4], int size);
int sum(int (*arr)[4], int size);
// data的本质是 3个[(指向4个int组成的数组)的指针]的数组
// 另一种声明二维数组的方式,数组的指针
char *a[5] = {
"abc", "aeg", "aeg"
};
int sum(char **arr, int size);
引用
左值,右值,左值引用,右值引用
左值是在内存中有实际地址的值,是可以出现在赋值号的左侧的。而右值说的是在赋值号右边的值,临时储存,不允许取地址,用后销毁。
左值引用比较常见,相当于给变量起一个别名
int a = 10;
int& refA = a; // refA是a的别名, 修改refA就是修改a, a是左值,左移是左值引用
int& b = 1; //编译错误! 1是右值,不能够使用左值引用
右值引用相当于给一个临时变量起了一个名字,将该变量的声明周期延长
int&& a = 1; //实质上就是将不具名(匿名)变量取了个别名
int b = 1;
int && c = b; //编译错误! 不能将一个左值复制给一个右值引用
class A {
public:
int a;
};
A getTemp()
{
return A();
}
A && a = getTemp(); //getTemp()的返回值是右值(临时变量)
可见,一般情况下,左值引用只能绑定左值,右值引用只能绑定右值。只有一个例外,就是常量左值引用是可以接收右值的,只是不允许进行改动。
右值引用是c++11的新特性,可以很大程度上优化代码。最常用的是在类中的移动构造函数和移动赋值函数,需要接受的形参是一个右值引用。可以配合std::move()
函数来进行优化。
std::move()
std::move()
的功能其实说起来很简单,就是将一个左值转化成右值。
对于一个左值,肯定是调用拷贝构造函数了,但是有些左值是局部变量,生命周期也很短,能不能也移动而不是拷贝呢?C++11
为了解决这个问题,提供了std::move()
方法来将左值转换为右值,从而方便应用移动语义。
需要注意的是,对于一个对象来讲,std::move()
了之后,并不会立刻析构。但是由于变成了右值,因此很可能会调用移动构造函数/移动赋值函数,这样的话,很可能将原来对象中的动态分配的成员释放。如果之后再进行引用,容易出现问题。