==============================================================================
1 指针可能的取值:
保存一个对象的地址。
eg:
int ival = 1024;
int *pi = &ival; //指针pi(指向int类型的指针) 指向对象ival的地址。
指向某个对象后面的另一个对象。
eg:
int *pa = 0; //指向int型的指针pa 未指向任何地方,初始化为0.
int ival = 1024;
int *pi = &ival; // 同上例。
pa = pi; // 现在pa 和 pi 指向同一个对象:ival。这样pa 就指向了
对象(指针 pi) 后的另一个对象(ival).
指针取0值。
eg:
int *pb = 0; //pb 指针被初始化为0,没有指向任何地方。
建议:
除非指针所指向的对象已经存在了,否则不要先定义指针。
如果要分开定义指针和及其所指向的对象,就将该指针初始化为0.
==============================================================================
2 指针的初始化和赋值操作:(只能取以下4种类型的值)
0值常量表达式:
即在编译时可获得0值的int 型 const 对象 or 字面值常量0.
所谓常量表达式,就是编译器在编译时,就能够计算出结果的整型表达式。
eg:
int *pi = 0;
const int c_ival = 0;
pi = c_ival;
类型匹配的对象的地址:
eg :
double dval;
double *pd = &dval; //OK:指针pd 指向的是double类型,与dval的类型匹配。
int *pi = 0;
pi = &dval; //ERROR: pi 指向的是int 类型,与dval的类型不匹配。
另一个对象之后的下一地址 和 同类型的另一个有效指针:
eg:
double dval;
double *pd = &dval;
double *pd2 = pd;
==============================================================================
3 void* 指针
void* 指针特殊之处在于,它可以保存任何类型对象的地址。
eg:
double obj = 3.14; //定义了一个double 类型的对象obj ,初始化为3.14
double *pd = &obj; //定义了一个指向double类型的指针pd,指向obj的地址。
void *pv = &obj; //OK:因为是void* 型。
pv = pd; //同上。
注意:不允许void*指针操纵它指向的对象。
==============================================================================
4 指针操作
对指针进行解引用可以访问它所指的对象。
eg:
string s("hello world");
string *sp = &s;
cout << *sp; //打印出hello world.
下面修改指针所指向的值:
eg:
*sp = "ByeBye";
cout<<*sp; //这样就不再打印hello world ,而变成ByeBye.
修改指针所指向的对象:
eg:
string new_s = “I love you";
sp = &new_s;
cout<<*sp; //修改了sp所指向的对象,现在变成了I love you。
关键:
如果有解引用操作,那么就是修改指针所指向的对象;如果没有,就是修改指针本身。
=============================================================================
5 指针和引用的区别
有2个区别:第一个是引用总是指向某个对象,定义 引用的时候 如果没有初始化那就是错误的。
第二个区别:(很重要)
给引用赋值修改的是: 它所关联的对象的值,而不是让它去与另一个对象相关联。这是与指针最主要的差别。
eg:
int ival =1024, ival2 = 2048;
int *pi =&ival, *pi2 = &ival2;
pi = pi2; //对于指针,赋值完成后,pi 所指向的对象ival的值保持不变,只是让pi 指向了另一个对象ival2.
eg:
int &ri = ival, &ri2 = ival2;
ri = ri2; //对于引用,赋值完成后,修改了ri引用的对象ival的值。但ri 和 ri2 所指向的对象仍然不变。
=============================================================================
6 指向指针的指针
指针本身,也可以是其他指针指向的内存对象。所以一个指针的地址也可以存放在其他指针中。
eg:
int ival = 1024;
int *pi = &ival; //指针pi 指向Ival
int **ppi = π //二级指针ppi 指向指针pi
cout << **ppi; //进行2次解引用就可以访问到 ival
=============================================================================
7 使用指针访问数组元素
在C++中,在表达式中使用数组名时,该数组名会自动转换为指向数组第一个元素的指针。
eg:
int ia[] = {0,2,4,6,8};
int *ip = ia; //这样就使得ip指向ia数组的第一个元素ia[0];
ip = &ia[4]; //使用下标操作,可以给元素定位,通过地址操作符获取该元素的存储地址。
也可以通过指针的 算数操作来获取指定内容的存储地址。
eg:
ip = ia; //指针ip 指向ia[0]
int *ip2 = ip + 4; //指针ip2指向 ia[4]
只要2个指针指向同一数组,C++还支持对着脸个指针进行减法操作:
eg:
ptrdiff_t n = ip2 - ip;
这里出现了一个新的数据类型,ptrdiff_t。它与size_t一样,都是机器相关的类型,定义在cstddef头文件中。
size_t 是unsigned 型,ptrdiff_t是signed int 型。
解引用和指针算术操作之间的相互作用。
eg:
int last = *(ia+4); //这样就获取了ia数组的最后一个元素。
注意,这里的括号是必须的,因为* 的优先级大于 + 号。
在数组中,用指针来模拟vector类型的begin()和end()函数。
eg:
const size_t arr_size = 5;
int arr[arr_size] = {1,2,3,4,5};
int *p = arr; //p指向数组arr的第一个元素arr[0]
int *p2 = p + arr_size; //p2指向数组末尾的后一位。
在C++中,允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作。
现在来模拟vector的begin() 和 end():
eg:
const size_t arr_sz = 5;
int int_arr[arr_sz] = {0,1,2,3,4};
for(int *pbegin = int_arr, *pend = int_arr+arr_sz; pbegin !=pend; pbegin++)
{
cout<< *pbegin<<' ' ;
}
pend类似与哨兵的作用。
==============================================================================
8 指针与const 限定符
讨论2种指针:指向const对象的指针 and const指针。
对于第一种,指向const对象的指针:
如果指针指向const对象,那么就不允许用指针来改变它所指向的const值。所以,C++强制要求指向const对象的指针
也必须具备const特性!
eg:
const double *cptr; //指针cptr指向一个double类型,const对象。
注意,这里只是说,cptr所指的对象是const类型的,而cptr本身不是const类型。在定义时,不需要对cptr进行初始化。
只允许让cptr 指向另一个const对象,不允许通过cptr 修改它所指对象的值。
eg:
*cptr =42; 这就是错误的,因为cptr是一个指向const对象的指针。
相应的,把一个const 对象的地址,赋给普通的指针,也是错误的(所以说const变量 与 指向const对象的指针是一一对应的)
eg:
const double pi = 3.14; //const 变量 pi,为double 类型
double *ptr = π //ERROR
const double *cptr = π //OK
对于void* 指针,对于要保存const 对象的地址,也必须使用const void*类型。
eg:
const int universe = 42;
const void *cpv = &universe; //OK
void *pv = &universe; //ERROR
注意,允许把非const 对象的地址赋值给 指向const对象的指针。
eg:
double dval = 3.14; //非const 对象 dval
const double *cptr; //指向const对象的指针 cptr
cptr = &dval; //OK, 但是不能通过cptr 来修改dval的值。
注:不能使用指向const对象的指针来修改基础对象,但如果该指针指向一个非const对象,可以用其他方法改变它所指的对象。
eg:
double dval = 3.14159;
const double *cptr = &dval;
double *ptr = &dval;
*ptr = 2.72;
cout<<*cptr; // 2.72
该例子说明,虽然无法直接通过cptr 来修改dval 的值,但可以通过一个普通指针进行转换,达到修改的目的。
在实际应用中,指向const对象的指针常常作为形式参数,将形参定义为指向const对象的指针,可以确保传递给函数的实际对象,不会
因为形式参数的修改而修改。
对于第二种:const指针:
C++ 提供const指针,该指针本身的值不能被修改。
eg:
int errNumb = 0;
int *const curErr = &errNumb;
上面这句话,可以读作:curErr 是指向int型对象的const 指针。
const指针的值不能修改,不能使curErr指向其他对象。const指针必须在定义时初始化。(我觉得很像引用)
与指向const对象的指针不同的是,const 指针可以通过解引用操作,修改它所指对象的值。
eg:
*const curErr = 1; //OK
拓展:指向const对象的const指针
eg:
const double pi = 3.14159;
const double *const pi_ptr = π
这里的pi_ptr 既不能修改它所指对象的值,也不允许修改它的指向。
可以读作: pi_ptr 是一个const 指针,指向double类型const 对象。(感觉没啥用,就是比较绕)。
===============================================================================
9. 指针与typedef
eg:
typedef string *pstring;
const pstring cstr;
问:cstr 是什么类型? 很多人认为,cstr是一种指针,指向string类型的const对象。
即const string *cstr(我也是这么认为的)但这是错误的。
原因如下:
误将typedef 当作文本扩展了。
声明const pstring时,const 修饰的是pstring 类型。所以说,该声明语句应该是把cstr 定义为指向string 类型对象的const 指针。
即:
string *const cstr;
所以好的建议就是,在用typedef 写 const 类型定义的时候,将const 限定符放在类型名之后,就不会产生混淆了。
eg:
string s;
typedef string *pstring ;
pstring const cstr2 = &s;
这样就很容易理解 cstr2 的类型了,即:指向string 对象的const 指针。
===============================================================================