new & delete

在声明一个新的之神变量的时候,最好需要用 new 来分配一块合适的内存,并且在使用结束之后用 delete 进行删除。在cpp中,对自定义的数据类型使用 newdelete,会分别调用解析函数和析构函数。
应当尽量避免未分配内存直接声明指针变量

  1. // 声明和释放指针变量
  2. int* a_pt = new int;
  3. delete a_pt;

动态数组

  1. // 用new创建动态数组
  2. int* psome = new int [10];
  3. delete [] psome;

动态数组的使用方式,其实基本等同于正常的数组

  1. *psome; // 数组的第一个元素
  2. psome[i]; //数组的第i个元素
  3. // 指针允许进行加减操作,对应的是移动相应的变量类型位置
  4. psome += 1; // 此时psome指向数组中第一个元素的位置

!注, 对于一般的数组声明,其变量名(my_array)也等价于 &my_array[0]。但是数组的便俩革命不允许进行加减操作

  1. int my_array[10]={0};
  2. std::cout << my_array << std::endl;
  3. // 0x61fd70
  4. my_array + 1; // not valid

指向对象(结构体)的指针

该类指针指向对象的首地址,引用该类内部成员的话,使用 -> 运算符。

  1. struct person
  2. {
  3. std::string name{"Haymax"};
  4. int age=18;
  5. };
  6. person *me = new person {"Haymax", 21};
  7. std::cout << me->age;
  8. std::cout << (*me).age;
  9. delete me;

数组的动态联编和静态联编

在声明的时候,使用形如 int array[10] {0}; 进行定义的数组使用的是静态联编,在编译的时候就分配了内存。而使用 int *array = new int [10],是在运行过程中动态进行内存分配和回收的,因此必须加上 delete 释放内存。

指针常量和常量指针

指针常量——指针类型的常量

  1. int *const a;

代表的是变量 a 是一个常量,a 的类型是一个 int指针。也就是说 a 的值(它指向的地址)是不可以进行更改的,但是该地址存储的值是可以更改的。

  1. int *const a;
  2. int *b = new int;
  3. *a = 10; //合法
  4. a = b; //非法

常量指针——指向常量的指针

  1. const int *a;
  2. int const *a;

常量指针说的是,该指针变量指向的是一个常量。也就是说,指针所指向的地址是可以改变的,但是地址中的值是常量不可改变。常用于函数参数传递,尤其是数组。

使用 const 之后,函数参数传入之后会自动生成一个副本。

  1. int sum_arr(const int *begin, const int *end);
  2. int sum_arr(const int *begin, const int *end) {
  3. const int *ptr;
  4. int sum=0;
  5. for (ptr=begin; ptr!=end; ptr++){
  6. sum += *ptr;
  7. }
  8. return sum;
  9. }
  10. int cookies[5] = {1, 2, 3, 4, 5};
  11. int all_cookies = sum_arr(cookies, cookies+5);

在上述代码中,传入的函数中的值是不可更改的常量。也即对 *ptr 进行赋值是非法的。

上述代码中,声明 ptr 的时候的 const 关键字是必须的,因为在定义函数的时候,用的是 const int *begin,必须对应。否则在 ptr=begin 的赋值操作会报错。

总结:
事实上,可以将普通变量的地址给到 const 指针,只不过此时,对于该指针而言,它指向的地址中的值是不可更改的。也可以将 const 变量的地址给 const 指针。
但是,将 const 变量的地址给普通指针是不可行的,在编译的时候会报错,比如下面的例子:

  1. const int a = 0;
  2. int *ptr = &a; // 非法

二维数组传参

  1. int data[3][4] = {
  2. {1, 2, 3, 4},
  3. {2, 3, 4, 5},
  4. {3, 4, 5, 6}
  5. };
  6. // 正确的函数原型
  7. int sum(int arr[][4], int size);
  8. int sum(int (*arr)[4], int size);
  9. // data的本质是 3个[(指向4个int组成的数组)的指针]的数组
  10. // 另一种声明二维数组的方式,数组的指针
  11. char *a[5] = {
  12. "abc", "aeg", "aeg"
  13. };
  14. int sum(char **arr, int size);

引用

左值,右值,左值引用,右值引用

左值是在内存中有实际地址的值,是可以出现在赋值号的左侧的。而右值说的是在赋值号右边的值,临时储存,不允许取地址,用后销毁。
左值引用比较常见,相当于给变量起一个别名

  1. int a = 10;
  2. int& refA = a; // refA是a的别名, 修改refA就是修改a, a是左值,左移是左值引用
  3. int& b = 1; //编译错误! 1是右值,不能够使用左值引用

右值引用相当于给一个临时变量起了一个名字,将该变量的声明周期延长

  1. int&& a = 1; //实质上就是将不具名(匿名)变量取了个别名
  2. int b = 1;
  3. int && c = b; //编译错误! 不能将一个左值复制给一个右值引用
  4. class A {
  5. public:
  6. int a;
  7. };
  8. A getTemp()
  9. {
  10. return A();
  11. }
  12. A && a = getTemp(); //getTemp()的返回值是右值(临时变量)

可见,一般情况下,左值引用只能绑定左值,右值引用只能绑定右值。只有一个例外,就是常量左值引用是可以接收右值的,只是不允许进行改动。
右值引用是c++11的新特性,可以很大程度上优化代码。最常用的是在类中的移动构造函数和移动赋值函数,需要接受的形参是一个右值引用。可以配合std::move()函数来进行优化。

std::move()

std::move()的功能其实说起来很简单,就是将一个左值转化成右值。
对于一个左值,肯定是调用拷贝构造函数了,但是有些左值是局部变量,生命周期也很短,能不能也移动而不是拷贝呢?C++11为了解决这个问题,提供了std::move()方法来将左值转换为右值,从而方便应用移动语义。
需要注意的是,对于一个对象来讲,std::move()了之后,并不会立刻析构。但是由于变成了右值,因此很可能会调用移动构造函数/移动赋值函数,这样的话,很可能将原来对象中的动态分配的成员释放。如果之后再进行引用,容易出现问题。