The C++ Programming Language 第五章 学习笔记
james chen/050220
*********************
5.1指针
*********************
对于类型T,T*就是"到T的指针",也就是说,一个类型为T*的变量能保存一个类型为T的对象的地址。
char c='a';
char *p=&c; //将c的地址送给char指针p
cout<<c<<","<<*p<<endl; //两个值一样
*p='h'; //间接修改了c,因为p=&c,(*p='h')==(c='h')
cout<<c<<endl; //h
很郁闷的是:到T*、函数的指针,指针数组,玩起来更复杂,哎,go:
一、到T*的指针
[到(T类型的指针)的指针],这样理解可能要好一些。
int i=123;
int *p1=&i;
//int** p2=p1; //错,p1只有一个*,故不能赋值
int** p2=&p1; //正确,取p1指针的地址送给p2,相当于int**,刚好与p2匹配
cout<<&i<<endl;
cout<<p1<<endl; //p1和i地址相同
cout<<&p1<<endl; //&p1为p1指针的地址,与p1不同,p1是所指对象i的地址
cout<<p2<<endl; //&p2为p2指针的地址,与p1相同
//cout<<*p2<<endl; //错,*p2只是取得p2指针所指对象的地址
cout<<*(*p2)<<endl; //正确,取得p2指针所对对象的值
//也可以写成**p2
**p2=321; //给p3指针所指的指针赋值
cout<<i<<endl; //321
cout<<*p1<<endl; //321
延伸学习:
指向T*的指针的指针的指针的......
int a=123;
int *b=&a;
int **c=&b;
int ***d=&c;
int ****e=&d;
int *****f=&e;
*****f=54321; //54321
cout<<a<<endl; //54321
cout<<*b<<endl; //54321
cout<<**c<<endl; //54321
cout<<***d<<endl; //54321
cout<<****e<<endl; //54321
cout<<*****f<<endl; //54321
//爽啊,原来是这样的,爽够了该继续了。。。
二,指针数组,即数组元素为指针。
int* a[10]; //声明一个数组,内含10个元素,为int型指针
for(int i=0;i<10;i++)
a[i]=new int(100+i); //申请int型内存空间,并赋值100+i,再将地址送给a[i]
for(i=0;i<10;i++)
cout<<a[i]<<endl; //显示数组每个元素(指针)的地址
for(i=0;i<10;i++)
cout<<*a[i]<<endl; //显示值,为100,101,102,103....109
三,指针与函数
1,指针作为函数的参数
void hehe(int *x)
{*x+=100;}
int a=23;
hehe(&a);
cout<<a<<endl; //123;
int *b=new int(a); //申请一内存空间,赋值为a
hehe(b);
cout<<*b<<endl; //223;
2,指针型函数,即返回一个指针
int *haha(int *x)
{*x+=100;return x;}
int a=1;
int *b=haha(&a); //将返回的地址送给*b
cout<<*b<<endl; //101
cout<<haha(b)<<endl; //0x00....显示返回指针地址,在这里与b地址相同
cout<<*b<<endl; //201
cout<<*haha(b)<<endl; //301
int c=*haha(b); //将返回地址取值*haha(b),再将值送给int变量
cout<<c<<endl; //401
3.指向函数的指针
就是说将一个函数的首地址赋值给一个函数指针,然后即可用此函数指针操作函数。。
int (*add)(int a);
int add1(int a){return a*10;}
int add2(int a){return a*100;}
int add3(int a){return a*1000;}
add=add1;
int i=2;
int a=add(i);
add=add2;
int b=add(i);
add=add3;
int c=add(i);
cout<<a<<","<<b<<","<<c<<endl; //20,200,2000,
延伸学习:
函数指针数组,主要是觉得每次赋值太过累赘
int add1(int a){return a*10;}
int add2(int a){return a*100;}
int add3(int a){return a*1000;}
int (*add[3])(int a)={add1,add2,add3}; //在声明时就将add1,add2,add3压进去
for(int i=0;i<3;i++)
cout<<add[i](2)<<endl; //20,200,2000
注意:函数指针和函数指针数组都只是指针,不是函数,所以千万不要搞个函数体画蛇添足。
//爽啊,这可以俺自己试出来的哦,至少现在俺看过的书上还没有这种玩法,当然,肯定是自己看书太少。
*********************
5.2数组
*********************
数组的初始化
int a[3]={1,2,3}; //正确
//int a[3]={1,2,3,4}; //错误,数组a只有三个元素
int a[5]={1,2}; //正确,相当于int a[5]={1,2,0,0,0};
int a[]={1,2,3,4,5,6}; //正确
a[]={1,2,3,4}; //错误,给一个不存在的数组赋值
5.2.2字符串文字量
字符串文字量是用两个双引号括起来的字符序列,如:
"How are you!!"
一个字符文字量的实际长度要比它所包含的字符个数要多一个,因为它总是由'/0'结尾,如:
szieof("love")==5
字符串文字量的类型是“适当个数的const字符的数组”,所以"love"的类型其实就是const char[5]。
可以用字符串文字量给一个char*赋值,但不能用这个char*去修改字符串文字量,如:
char *a="how are you!!";
cout<<a<<endl; //how are you!!
//a[0]='a'; //错,给常量赋值,但编译器不会提示
记住:把字符串文字量当成常量。
如果想要修改字符串,就需要将它复制到一个char数组里:
char a[]="how are you!!";
cout<<a<<endl; //how are you!!
a[3]='a';
cout<<a<<endl; //howaare you!!
字符串文字量是静态分配的,所以可以让函数返回:
const char* aa()
{return "haha";}
const char *b=aa;
cout<<b<<endl; //haha
为了使程序整洁,可以用空白字符将长的字符串分成几个部分,如:
char *p="aaaaaa"
" bbbbbb" " ccccc";
cout<<p<<endl; //aaaaaa bbbbbb ccccc
*********************
5.3指针与数组
*********************
在c++中,指针与数组密切相关。一个数组的名字即是该数组的首地址,也就可以赋值给同类型的指针。
char x[10]="aaabbbccc";
char* a=x; //将x的首地址赋值给*a
char* b=&x[3]; //将x的第4个元素的地址赋值*b
char* c=&x[6]; //将x的第7个元素的地址赋值*c
cout<<a<<endl; //aaabbbccc
cout<<b<<endl; //bbbccc
cout<<c<<endl; //ccc
5.3.1:在数组里漫游
访问数组可通过下标与指针两种方式:
char a[5]="abcd";
for(int i=0;i<4;i++) //i<4亦可换成*b!=0,结束符为0
cout<<a[i]<<endl; //用下标遍历数组
char *b=a;
for(i=0;i<4;i++) //i<4亦可换成*b!=0,结束符为0
cout<<*b++<<endl; //用指针遍历数组
char *c=a;
while(cout<<*c++<<endl,*c!=0); //用指针遍历数组
只有当两个指针指向同一数组元素时,指针之间相减才有意义。如果从一个指针减去另一指针,结果就是这两个指针之间的数组元素个数(一个整数)。指针加或减一个整数,得到的仍是一个指针,如果运算所得指针地址超出数组边界,那这个值是无定义的。
int a[10];
int b[10];
int i=&a[5]-&a[2];
cout<<i<<endl; //3,间隔3个元素
i=&a[5]-&b[2];
cout<<i<<endl;
int* p=a+2; //p=a[2];
p=a-2; //出界,*p没定义
郁闷的是,这个出界的指针居然可以使用,怪哉:
cout<<*p<<endl; //随机值
*p=4321;
cout<<*p<<endl; //4321
*********************
5.4常量
*********************
常量即不变化的值,一经声明,便可直接使用,但不能修改。
常量声明时必须初始化。
const int a=123;
const int b[]={4,3,2,1};
const int c; //出错,未初始化
a=200; //出错,常量不能修改
b[2]=34; //出错,常量不能修改
常量主要用在一些初始化后便不用改变的值,如:
pi圆周率,固定值,不用改变。
一些对象的值。
函数形参,只读不写。
int add100(const int a)
{
a+=100; //出错,不能改变a的值
return a+100; //OK,未改变a的值
}
5.4.1:指针和常量
使用一个指针时涉及到两个对象:指针本身和所指针对象。
const * 将使对象成为常量。 //个人理解为:指针常量
* const 将使指针成为常量。 //个人理解为:常量指针
例:
int i=100;
int o=123;
//const *
int const *p=&i; //使对象成为常量
cout<<*p<<endl; //100
*p=123; //错,对象为常量,不能赋值
p=&o; //OK,指针不是常量,可以改变地址
cout<<*p<<endl; //123
//* const
int *const p1=&i; //常指针
cout<<*p1<<endl; //100
*p1=123; //OK,对象非常量,可以改
cout<<*p1<<endl; //123
p1=&o; //错,指针为常量,不能改
const int *const p2=&o; //到const的const指针
*p2=33; //错,p2指向的是常量
p2=&i; //错,p2本身也是常量
cout<<*p2<<endl; //123
*********************
5.5引用
*********************
引用就是对象的另一个名字。引用的主要用途是为了描述函数的参数和返回值,还有运算符重载。
int a=123;
int& b=a;
int c=b;
cout<<a<<","<<b<<","<<c<<endl; //123,123,123
b=234;
cout<<"a:"<<a; //234,因为b引用了a,b=234就相当于a=234
一个引用的地址在初始化后就不能改变了,它总是指向初始化时所指的那个对象。
所以用作形参会很爽。
注意:不能对一个引用声明时赋值。
int &m=234; //错,常规引用需要一内存单元地址,故不能直接赋值。
但可以用const T&来在声明引用时直接赋值:
const int &m=234;
const T&的实际过程:
1,右值到T类型的隐式转换。
2,将右值放到一个T类型的临时变量中。
3,将此临时变量地址作为初始式的值。
这个临时变量一直存在,直到这个引用的作用域结束。
Bjarne Stroustrup忠告:
.为了提高程序的可读性,通常应该尽可能避免让函数去修改它们的参数。
.如果将“普通”引用参数用于某些函数,那么这些函数的名字就应该给出其引用参数将被修改的强烈提示。
*********************
5.6指向void的指针
*********************
一个指向任何对象类型的指针都可以赋值给类型为void*变量,void*可以赋值给另一个void*,两个void*可以比较相等与否,而且可以显式地将void*转换为另一类型。
int *p=new int(123);
void *a=p; //赋值给void指针
void *b=a; //void指针赋值给另一void指针
cout<<a<<endl;
cout<<b<<endl;
cout<<p<<endl; //三个地址都一样
cout<<*a<<endl; //出错,不能间接引用
a++; //出错,不能增量,不知道a的大小
int *t=a; //出错,不能隐式转换
int *t=static_cast<int*>(a); //OK,可以显式转换回int
void*的最重要的用途是需要向函数传递一个指针,而又不能对对象的类型做任何假设。还有就是从函数返回一个无类型的对象,当然,要使用该对象,必须显示类型转换。
/*
个人理解:将一个对象赋值给void指针后,可以看到两个地址都一样,我想应该可以理解为,void指针是该对象的一个引用,只是类型不明而已,对这个void指针不能使用*,++等取值操作,否则引起错误。
*/
*********************
5.7结构
*********************
数组是相同元素的聚集,而stuct则是任意类型元素的一个聚集。
struct address
{
char *name;
int id;
};
对struct的变量声明和其他类型一样:
address a;
a.name="i love bb!!";
a.id=4321;
对结构类型变量的初始化也可以与数组一样:
address b={"hehe",321};
结构型指针的使用:
address *c=&a;
cout<<c->name<<endl; //i love bb!!
cout<<c->id<<endl; //4321
cout<<(*c).name<<endl; //i love bb!!
cout<<(*c).id<<endl; //4321
address d=a; //struct赋值给struct
d=*c; //struct*赋值给struct
在对struct类型指针的使用时,指针->成员 == *指针.成员
*********************
5.8 BS的忠告
*********************
1.避免非平凡的指针技术。
2.当心,不要超出数组的界线去写。
3.尽量使用0而不是NULL。
4.尽量使用vector和valarray而不是内部数组。
5.尽量使用string而不是以0结尾的char数组。
6.尽量少用普通的引用参数。
7.避免void*,除了在某些低级代码里。
8.避免在代码中使用非平凡的文字量("神秘的数"),应该定义和使用各种符号常量。