复合类型
引用
引用(reference)不是对象,相反的,它只是给已存在的对象起的另外一个名字
引用的特点:
1.一个变量可取多个别名
2.引用必须初始化
3.引用只能在初始化的时候引用一次,不能重新赋值
//格式: 类型 &引用名=已定义对象名
int i=42;
int& p=i; //p是i的另一个名字
int &q=i; //&符号的位置可以是和int连在一起,也可和q连在一起
int &m; //!!报错!!该引用没有初始化
int ii=q; //获取引用的值
指针
指针(pointer)是对象,可以赋值和拷贝,它存放的还是某个对象的地址;指针可以解引用
指针的状态:
1.指向一个对象
2.执行紧邻对象的空间的下一个位置
3.空指针
4.无效的指针,上述情况之外的指针
int i=42;
int *p ;//定义一个int型对象的指针
p=&i;//将i的地址赋给p
std::cout<<*p; // * 是取出存在P指向地址的变量
没有初始化的指针会引发运行时错误,因为指针所占的内存空间的内容被视为一个地址值,而这个位置本是不存在的,或者这个位置有内容,但是这个内容被当做地址取访问了,这样就会导致程序崩溃
空指针
int *p1 = nullptr; //等价于 int *p1 = 0;
int *p2 = 0; //直接将p2初始化为字面常量0
//需要首先#include cstdlib
int *p3 = NULL; //等价于 int *p3 = 0;
void*指针
void是一种特殊的指针类型,可以存放任意对象的地址。但是,与通常指针不同的是我们不清楚它对象的类型,这也就导致不能直接操作它指向的对象。从void的视角来看,内存空间仅仅是内存空间,无法访问内存空间中所存的对象。
void指针用途:
1.与其他指针比较
2.作为函数的输入和输出
3.赋值给另一个void指针
探究:指针的赋值和引用的“赋值”
#include<iostream>
#include<stdlib.h>
using namespace std;
int i=42;
int j=50;
int k=60;
int &p=i;
int *q=&i;
int main()
{
cout<<"初始i的值及地址:"<<i<<" "<<&i<<endl;
cout<<"p引用对象及地址:"<<p<<" "<<&p<<endl;
cout<<"q指向对象及地址:"<<*q<<" "<<q<<endl;
cout<<endl;
p=j; //对引用p操作
cout<<" 引用p等于j:"<<p<<" "<<&p<<endl;
cout<<"此时i值及地址:"<<i<<" "<<&i<<endl;
cout<<"此时j值及地址:"<<j<<" "<<&j<<endl;
cout<<"此时指针q指向:"<<*q<<" "<<q<<endl;
cout<<endl;
q=&k; //对指针q操作
cout<<" 指针q指向k:"<<*q<<" "<<q<<endl;
cout<<"此时i值及地址:"<<i<<" "<<&i<<endl;
cout<<"此时p引用对象:"<<p<<" "<<&p<<endl;
system("pause");
}
输出的结果:
初始i的值及地址:42 00359000
p引用对象及地址:42 00359000
q指向对象及地址:42 00359000
引用p等于j:50 00359000
此时i值及地址:50 00359000
此时j值及地址:50 00359004
此时指针q指向:50 00359000
指针q指向k:60 00359008
此时i值及地址:50 00359000
此时p引用对象:50 00359000
结论:
p是i的引用,“赋值”引用p令p=j,若引用可以赋值,p等于新的值的同时i值不变,但是从输出结果中看出,i=j;所以看似是对p赋值,实际是对对象i重新赋值,也就是说明了引用初始化后便和对象绑在了一起,引用不能重新赋值。引用的“赋值”实际是对它对象的重新赋值。
同样的观察指针,指针q原本指向i,指向i的地址,重新赋值后指向j,指向j的地址,且对i没有影响。说明,指针可以重新赋值
复合类型的声明
复合类型(compound type)
基本数据类型(base type)
声明符(declarator)
要坚持自己的编程风格,不要总是变来变去
//写法1
int* p1,*q1,j,k; // * 只是修饰p1,对该声明语句中的其他变量不起作用
//写法2
int *p1,*q1,j,k;
//写法3
int *p1;
int *q1;
int j,k;
指向指针的指针
指针是内存中的对象,也和其他对象一样,有它的地址,所以允许将该指针的地址在存放到另一个指针里。通过*的个数可以区分指针级别。如以下代码,***表示指针的指针,***表示指向指针的指针的指针,以此类推。
代码如下
#include<iostream>
#include<stdlib.h>
using namespace std;
int i=42;
int *j=&i;
int **jj=&j;
int ***jjj=&jj;
int main()
{
cout<<*j<<endl;
cout<<*jj<<" "<<**jj<<endl;
cout<<*jjj<<" "<<**jjj<<" "<<***jjj<<endl;
system("pause");
}
输出结果:
42
00FD9000 42
00FD9004 00FD9000 42
指向指针的引用
同上,由于指针本身也是对象,所以也可以引用,它的形式为: *&r=j ; (j为指针)
代码如下:
#include<iostream>
#include<stdlib.h>
using namespace std;
int i=42;
int *j=&i; //声明j时,*说明j是指针类型;而输出时 * 表示解引用,就是取出存放在指针里的地址所对应的变量
int *&r=j; //r是指向指针j的引用,也就是r是j,*r是*j
int main()
{
cout<<j<<" "<<*j<<endl;
cout<<r<<" "<<*r<<endl;
system("pause");
}
输出结果:
00169000 42
00169000 42
const限定符
const的好处就是避免程序运行过程中改变了变量的值,相当于是定义了一个常量,例如圆周率pi,我们就可以设定为const
const的特点:
创建const对象时必须初始化,一旦创建就不能再改变
const对象只能执行不改变其内容的操作
const对象的调用:
默认下,多个文件出现的同名const是相互独立的,如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字
多文件共享const对象,代码如下:
//file_1.cc文件
extern const int i=3.14; //定义并初始化const i
//file_1.h头文件
extern const int i; //声明const i
顶层const与底层const
const double pi=3.14; //pi为常量,其值不能改变
double *ptr=π //报错:此处ptr为普通指针
const double *cptr=π //cptr指向双精度常量
*cptr=42; //报错:不能给*cptr赋值
//通常,指针类型必须与其所指对象类型一致,但是有两种例外,一是允许一个指向常量的指针指向一个非常量对象:
double dval=3.14; //dval是双精度浮点数,无const限定符,其值可改变
cptr=&dval; //正确,但是不能通过cptr改变dval的值
顶层const表示指针本身是常量
底层const表示指针所指对象是常量
int i=42;
const int a=i; //创建const对象并初始化
const int &r1=i; //允许const int&绑定到一个普通int对象上
const int &r2=50; //r2是一个常量引用
const int &r3=r1*2; //r3是一个常量引用