[C++] 引用和指针
原文die了
引用
引用(reference) 就是给对象起别名。对引用的操作与对变量直接操作完全一样。
引用定义时就需要初始化。允许一条语句定义多个引用,其中每个引用标示符都必须以符号&开头。
int value = 1;
int &refValue = value; // refValue 指向value
int &refValue2; // 报错,引用必须初始化
引用的写法也还可以是这样:
int value = 1;
int& refValue = value; // refValue 指向value
int& refValue2; // 报错,引用必须初始化
注意&符号和int之间是否紧挨着,两种写法都是对的(下面介绍的指针也可以用这两种写法), 这个只是个人的代码风格问题,并无实质差异。
引用更为常见的用法应该是作为函数的参数,利用引用的特性,可以让一个函数返回多个"返回值"。另外,当使用一个类作为函数参数时,比较好的做法也是使用引用。这样可以减少创建类副本的一些开销。请看一个具体的示例:
#include <iostream>
using namespace std;
const double PI = 3.14;
// 圆的数据结构
struct Circle{
double radius; // 半径
double circumference; // 周长
double square; // 面积
};
// 给出圆的半径,计算圆的周长和面积,返回是否计算成功。
bool CalCirle(Circle& c){
// 给的圆的半径为负数,返回计算错误
if (c.radius <= 0){
return false;
}
// 周长的计算
c.circumference = 2 * PI * c.radius;
// 面积的计算
c.square = PI * c.radius * c.radius;
return true;
}
int main(){
Circle c;
cout << "请输入圆的半径:";
cin >> c.radius;
if (CalCirle(c)){
cout << "该圆的周长是" << c.circumference<< endl;
cout << "该圆的面积是" << c.square << endl;
}
else{
cout << "您输入的半径不合法!" << endl;
}
return 0;
}
指针
指针(pointer) 是“指向(point to)”另外一种类型的复合类型。 指针存放某个对象的地址,要想获得该地址,需要使用取地址运算符(操作符&)。
注意这里的取地址运算符&和声明引用时标示符以符号&开头的意义和用法是不一样的。
int value = 2;
int* p = &value; // p存放变量value的地址,或者说p是指向变量value的指针
利用指针访问对象:如果一个指针指向一个对象,则允许解引用符(操作符*) 来访问该对象。
int value = 2;
int* p = &value; // p存放变量value的地址,或者说p是指向变量value的指针
*p = *p * 3; // 取这个变量,并乘以3
cout << *p << endl; // 输出结果为6
cout << value << endl; // 输出结果为6
指针可以为空(引用是不可以的)。空指针不指向任何对象,一个良好的习惯就是使用指针前,一定要判断是否为空。
得到空指针最直接的方法是用字面值nullptr来初始化指针。(C++11引入的一种方法)
以前的方法是将指针初始化为字面值0或者NULL。(NULL为一个预处理变量,它的值就是0)。
void*指针 是一个特殊类型的指针,它可用于存放任意对象的地址。
指针的指针 指针级数是没有限制的。但常见的也就是一级指针和二级指针,二级以上比较少见。给出一个多级指针的例子:
#include <iostream>
using namespace std;
int main(){
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int b[3][4] = {
11, 22, 33,
44, 55, 66,
77, 88, 99,
111, 222, 333
};
int *p1 = nullptr, *p2 = nullptr, **p3 = nullptr;
p1 = a; // p1指向a数组
p3 = &p1; // p3指向p1
for (int i = 0; i < 10; ++i){
cout << *(*p3 + i) << " "; // 输出: 0 1 2 3 4 5 6 7 8 9
}
cout << endl;
for (p1 = a; p1 - a < 10; ++p1){
p3 = &p1;
cout << ** p3 << " "; // 输出: 0 1 2 3 4 5 6 7 8 9
}
cout << endl;
for (int i = 0; i < 3; ++i){
p2 = b[i];
p3 = &p2;
for (int j = 0; j < 4; ++j){
cout << *(*p3 + j) << " "; // 输出:11 22 33 44 55 66 77 88 99 111 222 333
}
}
return 0;
}
函数指针 是C++中最大优点之一了。一般来说,使用函数指针比函数引用更为方便一些。
函数指针的声明使用方式:
<想要指向的函数之 返回类型>(*函数指针的 名称)<想要指向的函数之 参数类型…>
#include <iostream>
using namespace std;
void sayHello(const char* name){
cout << "Hello, " << name << endl;
}
int add(int a, int b){
return a + b;
}
int main(){
void(*pFunc1)(const char*);
pFunc1 = &sayHello;
pFunc1("Ace Tan"); // 输出:Hello, Ace Tan
int(*pFunc2)(int, int);
pFunc2 = &add;
cout << pFunc2(1, 2) << endl; // 输出3
return 0;
}
C++ 关键字Const Define Static
引用和const
const的引用 可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用(reference to const) 。
与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象。
int v = 123;
const int& r1 = v; // 允许将const int& 绑定到一个普通的int对象上
const int& r2 = 123; // 正确:r2是一个 常量引用
const int& r3 = r1 * 10 ; // 正确:r3是一个常量引用
int& r4 = r1 * 5; // 错误:r4是一个普通的非常量引用
int& r5 = v; // r5绑定对象v
r5 = 1; // 正确,r5并非一个常量
r1 = 1; // 错误:r1是一个常量引用,值不可更改
在实际编程中,如果写的语法有错误,IDE一般都是会提示的,根据提示修改就好。 但我们应该记住这些常见的特性。
指针和const
指针和const鬼混在一块,那才是噩梦的开始。
指针是对象,而引用不是。因此,可以像其他对象类型一样,允许指针本身定为常量。常量指针(const pointer) 必须要初始化。
int a = 0;
int* const p = &a; // p是一个常量指针,指向整型数a
const double pi = 3.14159;
const double* const pPi = π // pPi是一个指向常量对象pi的常量指针
是不是看着有点头晕,理清这些关系的最好方法是从右往左读 。
拿上面的第四个来解释一下。离pPi最近的是const,说明pPi是一个常量对象。对象的类型是什么呢,继续往左读,对象的类型由声明符的其余部分所决定的,声明符的下一个是*, 意思是pPi是一个常量指针。该声明语句的基本数据类型(double)确定了常量对象(pPi)指向了一个double型对象。再往左读还是个const,说明这个double型对象也是个常量。
C++ 中有两个术语对指针是否是常量,以及指针指向的对象是否是常量加以区分。
顶层const(top-level const) 表示指针本身是个常量。
底层const(low-level const) 表示指针所指的对象是一个常量。
这两个问题是独立的。
const的常见用法
const可以用来定义常量。但它更大的魅力是它可以修饰函数的参数、返回值,甚至函数的定义体。这也是实际应用中用的比较多的。
use const whenever you need
const修饰函数参数。const修饰参数,是防止传入的参数被意外的修改。前面讲到过,可以用引用或者指针作为参数来作为输出。 这样是不需要加const的。其他一些情况,如果传入的参数不需要被改变,则需要加const修饰。基本类型一般是不需要const进行修饰的,因为它采取值专递的方式。如果是用户自定义类型,我们一般采用引用传递,像 Function(A& a),其中A是用户自定义的类型。 Function(A a)的效率不如Function(A& a),因为函数体内将产生A类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。但这样声明有一个缺点,它会使调用者误认为出入的引用时可修改的,解决的方法就是加const进行修饰。
如果传入的参数不需要更改,那么加const修饰在实际编程中几乎是必须的。
const修饰函数的返回值。const一般用来修饰以"指针传递"方式的函数返回值,它表示函数的返回值(即指针)的内容是不可修改的。该返回值只能被赋给加const修饰的同类型指针。修饰以"值传递"方式的函数返回值是没有意义的。
class A {
private:
int num; // 成员变量
public:
void Func(int x); // 其实原型是两个参数 Func(A* const this, int x)
void Func2(int y) const;// 原型是Func2(const A* const this, int y)
};
void A::Func(int x){
num = num * x; // 正确:this所指向的对象没有const修饰,可以更改
}
void A::Func2(int y) const{
num = num * y; // 错误:this所指向的对象被const修饰,无法更改这个对象的数据。
}
至于const为什么放在后面,想必各位看官已经猜到了。原型的第一个参数是被省略了,无法修饰,大概也只能放到函数的后面了,虽然看起来怪怪的。
define 的常见用法
头文件保护(header guard)。在定义一个头文件时,建议习惯性地加上头文件保护,没必要太在乎你的程序到底需不需要。这是一个比较好的编程习惯。 一般预处理变量名我们用所要定义的类名的大写来命名。示例如下:
ifndef FORM_RIDE_H
define FORM_RIDE_H
// 你的代码
endif // FORM_RIDE_H
static
static定义的变量存储在静态数据区,在静态数据区,内存中所有的字节默认值都是0x00。
静态变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为0,使用时可以改变其值。与全局变量不同的是,静态变量或静态函数只有本文件内的代码才能访问它,它的名字在其它文件中不可见。
static的主要有以下三个特性:
表示代码退出一个块后,仍然能够存在的局部变量。因为数据会存储在静态数据区。
用来表示不能被其它文件访问的全局变量和函数。
表示属于一个类而不是属于此类的任何特定对象的变量和函数。这个和Java中static关键字的意义相同。
有了static关键字,变量就变的有些复杂了。是时候理清一下各类型变量的作用域范围了。
常见的变量分为如下几种:全局变量、静态全局变量、静态局部变量和局部变量。
全局变量 存储:静态存储区域。 作用域:在整个工程文件内都有效。
静态全局变量 存储:静态存储区域。 作用域:在定义它的文件内有效。
静态局部变量 存储:静态存储区域。 作用域:只在定义它的函数内有效。程序仅分配一次内存,函数返回后,改变量不会消失。
局部变量 存储:内存栈中。 作用域:只在定义它的函数内有效。程序返回后局部变量被回收。
static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝。
include
using namespace std;
int n = 100; // 全局变量
static int m; // 静态全局变量
class MyClass{
public:
void ChangeX(int x); // 普通成员函数
static void ChangeY(int y); // 静态成员函数
int GetY(); // 获取全局变量的值
private:
int m_x; // 普通成员变量
static int m_y; // 静态成员变量
};
void MyClass::ChangeX(int x){
++m_x;
static int times = 0;
++times;
cout << "第" << times << "次调用该函数" << endl;
}
void MyClass::ChangeY(int y){
//m_x = y; // 报错:静态成员函数只能引用静态成员变量
m_y = y;
}
int MyClass::GetY(){
cout << "静态成员变量y的值为:" << m_y << endl;
return m_y;
}
int MyClass::m_y = 0;//定义并初始化静态数据成员
int main(){
n = 88;
cout << "全局变量改为" << n << endl;
cout << "静态全局变量的初始值为" << m << endl;
MyClass cls1; // 创建第一个类
cls1.ChangeY(111); // 改变了静态成员的值
MyClass cls2; // 创建第一个类
cls2.ChangeX(1); // 连续调用3次
cls2.ChangeX(2);
cls2.ChangeX(3);
cls2.GetY(); // 直接输出静态成员变量看看
return 0;
}
输出结果: 全局变量改为88
静态全局变量的初始值为0
第1次调用该函数
第2次调用该函数
第3次调用该函数
静态成员变量y的值为:111
输出结果印证了上面所讨论的内容。

浙公网安备 33010602011771号