指针基础
引用
为对象另起一个名字,引用类型引用另外一种类型。
int a = 10;
int &b = a; //b指向a,是a的另一个名字
int &c; //错误:引用必须被初始化
- 引用只是和它的初始值绑定了,并不是被拷贝给引用了,一旦初始化完成,则无法重新绑定另一个对象;
- 引用只是别名,不是对象,所以不能定义引用的引用。
指针
是指向另一类型的复合类型,与引用类似,也实现了对其他对象的间接访问。
指针和引用的区别
- 指针本身就是一个对象,允许对指针赋值和拷贝;
- 在指针的生命周期内可以先后指向几个不同的对象;
- 指针无需在定义时就赋值(此时拥有一个不确定的值);
- 指针可以有多级,但是引用只有一级;
- 指针可以指向NULL,引用不可以;
- 指针++是指向指针后面的内存,引用++是引用对象自增;
- 指针在作为函数参数时需要检查是否为空,引用不需要。
//指针变量定义
int* p1, p2;
//p1是指向int对象的指针,但是p2是int对象
//不推荐同时定义多个指针变量
//获取对象地址:使用取值符&
int ival = 42;
int* p = &ival;
//指针类型必须要和它所指对象严格匹配
double dval;
double* pd = &dval;
int* pi = pd; //错误,指针pi的类型和pd不匹配
pi = &dval; //错误,试图把double对象的地址赋给int指针
指针的值应属于下列4种状态之一:
- 指向一个对象;
- 指向紧邻对象所占空间的下一个位置;
- 空指针,没有指向任何对象;
- 无效指针,上述情况之外。
//利用指针访问对象,使用解引用符*
int i = 42;
int* p = &i;
cout << *p << endl; //输出42
//给解引用赋值就是给指针所指对象赋值
*p = 3;
cout << *p << endl; //输出3
cout << *p << endl; //输出3
空指针
不指向任何对象。
//C++11标准引用,nullptr是一种特殊类型的字面值,可以被转换成任意其他指针类型
int* p = nullptr;
使用建议:
- 尽量定义了对象后再定义指向它的指针;
- 访问指针前先判空;
- 记得delete指针,且delete后将指针设置为空。
void指针
void*是一种特殊的指针类型,可用于存放任意对象的地址。
- 任何类型指针都可以直接赋值给void指针;
- 如果void类型指针赋值给其他类型指针,需要显式转换;
double boj = 3.14;
void* pv = &obj;
double* pd1 = pv; //错误,不可以直接赋值
double* pd2 = (double*)pv; //必须显式转换
- void指针可以直接和其他类型的指针比较指针存放的地址是否相同;
- void指针也可以用nullptr来初始化,表示一个空指针;
- void指针作为函数的输入和输出时,可以接受任意类型的输入指针和输出任意类型的指针(增加了泛编程的能力)。
直接delete void指针会导致内存泄露
delete voidP; //只是清空了一个指针 delete (FrameInfo*)voidP; //正确析构了voidP指向的变量
指针常量
不能修改指针所指向的地址,在定义时必须初始化。
char* const a = &p;
*a = 'a'; //可以
a = &b; //错误,指针指向的地址不能改变
常量指针
不能修改指针所指向地址的内容,但可以改变指针所指向的地址。
const char* a;
*a = 'a'; //错误,指针所指的值不能改变
a = &b; //可以
指针和数组
对数组的元素使用取地址符就能得到指向该元素的指针。
string nums[] = {"one", "two", "three"};
string* p1 = &nums[0]; //p1指向nums的第一个元素
string* p2 = nums; //等价于p2 = &nums[0]
指针与迭代器
vector和string迭代器支持的运算,数组的指针全部支持。
int arr[] = {0, 1, 2, 3, 4};
int* p = arr; //p指向arr[0]
++p; //p指向arr[1]
//使用指针遍历数组
int* e = &arr[5]; //此时e指向arr尾元素的下一个位置
for(int* b = arr; b != e; ++b)
cout << *b << endl;
//标准库函数begin和end
//上面的用法其实很容易出错,所以C++新标准引入了两个函数
int* beg = begin(arr); //指向a首元素的指针
inr* last = end(arr); //指向a尾元素的下一个位置的指针
//注意:尾后指针不能执行解引用和递增操作
指针运算
指针的位置改变或者计算指针与指针之间的距离。
函数指针
函数指针指向函数而非对象,函数的类型由它的返回类型和形参类型共同决定,与函数名无关。
int test1(int a){
return a;
}
int main(int argc, const char* argv[]){
int (*fp)(int a);
fp = test;
cout << fp(2) << endl;
return 0;
}
//函数指针可以作为参数传递给函数
int test2(int (*fun)(int), int b){
int c = fun(10) + b;
return c;
}
int main(int argc, const char* argv[]){
typedef int (*fp)(int a);
fp f = test;
cout << test2(f, 1) << endl;
return 0;
}
类中的指针
指向类对象的指针
可以使用指向类对象的指针,访问类对象的public方法和成员变量。
this指针
系统在创建对象时,默认生成的指向当前对象的指针。
基于this指针的自身引用还被广泛应用于那些智齿多重串联调用的函数中。比如连续赋值。
多态
总结来说,就是“一个接口,多种方法”。
- 静态多态:编译期间的多态,编译阶段决定运行哪个函数。
- 动态多态:运行时的多态,在程序执行期间判断所引用对象的实际类型,调用相应的方法。
- 虚函数:在基类中使用关键字virtual声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
- 纯虚函数:在基类中声明的虚函数,在基类中没有定义,但要求任何派生类都要定义自己的实现方法。用法是在虚函数后面加=0。可以使用纯虚函数定义接口类。