您的位置:首页 > 编程语言 > C语言/C++

TC++PL Chapter 5 读书笔记

2009-08-07 18:00 274 查看
Chapter 5

Pointers,Arrays,Structions

1.

指针

对于一个类型T,T* 就表示指向T类型的一个指针类型。也就是说,一个T*类型的变量可以存储 指向T类型的对象 的地址。

char c='a';

char* p=&c;

p中存储的就是c的地址。

对指针的基本操作是间接引用(dereference),操作符是 *,这里的地址是虚拟地址,而不是物理地址。

char c='a';

char* p=&c;

char c2=*p;

p存储的是c的地址,而c存储的是'a',那么赋给c2也就是*p的值就是'a'

大部分的机器都可以寻址到字节。

2.



使用普通的0,而不是NULL,一个为0的指针表示不指向任何对象的指针。

3.

数组

对于一个类型T,T[size] 表示size个T类型的元素的数组。元素通过0到size-1的下标被索引。

float v[3];

char* ch[10];

数组元素的数量必须被指定为一个常量,如果需要一个变量的边界,用vector

void f(int i)

{

int v1[i];//error

vector<i> v2(i);//ok

}

多维数组被表示为数组的数组

int d2[10][20];

4.

数组初始化

数组可以被一个元素的list初始化。

char ch[]={'a','b','c','d'};

int v[]={1,2,3,4};

如果没有指定数组的大小,就会计算初始化的元素的个数作为数组的大小。

如果指定了数组的大小,而初始化的元素个数多于这个大小,会报错,小于这个大小,其他的值将由0来填充。

这样的方式只能初始化,不能赋值。

一个char的数组可以方便得用一个字符串来初始化。

5.

字符串文字量

一个字符串文字量被双引号括起来

"this is a string."

它所包含的字符个数要比看上去多一个,以'/0'结尾,其值是0

sizeof("abcd")=5;

字符串文字量的类型是相应数量常量字符的数组,所以"abcd"的类型就是const char[5]

一个字符串文字量允许被赋值给char*,但是不能被修改。

如果想要一个字符串可以被修改,那么必须放在一个数组里

void f(){

char ch[]="Rene";

ch[0]='Z';

}

这样的做法是可以的

字符串文字量是静态分配地址的,作为函数的返回值是安全的。

const char* error_message(int i)

{

//.............

return "range error";

}

range error在error_message被调用之后不会被丢掉。

两个相同的字符串是否被分配到一起是由实现决定的

char* p="abcd";
char* q="abcd";
if(p==q)
std::cout<<"one!"<<'/n';

当是指针的时候==比较的两个地址的值,而不是所指向的对象

转义字符'/'同样可用

cout<<"beep at end of message/a/n";

/a表示的是发出某种声音。。。。

char alpha="abcd"

"ABCD";

这个表达式和char alpha="abcdABCD";是等价的。

"abcd/000adfdf"0后面的字符将被忽略

带L前缀的是const wchar_t[]型 强制转换有可能出现奇怪的情形。

6.

到数组的指针

Pointers into Arrays

数组和指针有密切的关系

int v[] = {1,2,3,4};
int* p1=v;// pointer to initial element (implicit conversion)
int* p2=&v[0]; // pointer to initial element
int* p3=&v[4]; // pointer to one beyond last element

数组名很容易用作指向其第一个元素的指针。但从数组到指针的转换会丢失其大小信息。

char p[20]="abcde";
char* q=p;
std::cout<<strlen(q)<<' '<<strlen(p)<<'/n';
std::cout<<sizeof(q)<<' '<<sizeof(p)<<'/n';

结果是

5 5

4 20

用strlen(),实际上传入strlen(p),strlen(q)的是同一个值,都是指针

数组作为参数传给函数时传的是 指针 而不是数组,传递的是数组的首地址,sizeof是操作符而不是函数。

而如果想要得到char *P="abcde",想要得到这个字符串的长度则必须用strlen()

vector和string没有这些麻烦事。。。。。。。

7.

在数组里漫游

访问数组元素,可以通过数组的一个指针在加上下标或者通过指向某元素的指针

void fi(char v[])

{

for(int i=0;v[i]!=0;i++) use(v[i]);

}

void fp(char v[])

{

for(char* p=v;*p!=0;p++) use(*p);

}

这两种效率上没有差别。。。。。。。

T* p;

那么p+1的整型值将比p的整形值大sizeof(T)

#include <iostream>
int main()
{
int vi[10];
short vs[10];
std::cout << &vi[0] << ´ ´ << &vi[1] << ´/ n´;
std::cout << &vs[0]<< ´ ´ << &vs[1] << ´/ n´;
}
produced
0x7fffaef0 0x7fffaef4
0x7fffaedc 0x7fffaede

指针间的减法只发生在这两个指针指向同一数组的两个元素的时候,(虽然没有足够快速的方法去检测),指针间减法的结果是这两个指针间元素的个数(是一个整形),一个指针可以加上或者减去一个整数,结果还是一个指针,如果这个指针指向的不是这个数组的一个元素也不是超出末端的一个位置的值,那么这个值产生的结果将是未定义的。例子

void f(){

int v1[10];

int v2[10];

int i1=&v1[5]-&v1[3];//i1=2

int i2=&v1[5]-&v2[3];//i2 undefined

int* p1=v2+2;//p1=&v2[2]

int* p2=v2-2;//p2 undefined

}

指针的加法没有意义并且是非法的

数组不具有自描述性,数组不一定保存着自身的长度,要遍历一个数组,必须提供元素的个数

void f(char v[],unsigned int size){

for(int i=0;i<size;i++) use(v[i]);

const int N=7;

char v2
;

for(int i=0;i<N;i++) use(v2[i]);

}

大多数的C++实现不提供数组的越界检查

8.

常量

常量可以让代码更具有可维护性。常量在初始化之后不会改变,因此在声明的时候就要初始化。

const int model=9;

const int v[]={1,2,3,4};

const int x;//error,需要初始化

如果编译器知道了常量的所有应用,甚至可以不为常量分配空间

const int c1=1;

const int c2=2;

const int c3=my_f(3);

extern const int c4;

const int* p=&c2;

c1,c2在编译时就可以知道他们的值,c3,c4不知道,所以要为他们分配空间,c2的地址被用到,因此c2也被分配空间

常量数组一般需要分配空间,因为编译器不知道表达式里使用的是数组中的哪些元素。

最常见的const用法是作为数组的下标和边界或者检测条件

const int a=1;

const int b=2;

const int max=10;

int v[max];

void f(int i){

switch (i){

case a:

//......

case b:

//.....

}

}

对于这类情况 也经常用enum来代替const

能用到const的地方尽量用const,使代码更具可维护性

9.

指针和常量

指向常量的指针与常量指针

void f(char* p)

{

char s[]="Gorm";

const char* pc=s;//指向常量的指针

s[2]='a';//不可以。。

pc=p;//可以

char *const cp=s;//指向数组s的指针常量

s[2]='a';//可以

cp=p;//不可以

const char *const cpc=s;//指向常量的常量指针

s[2]='a';

cpc=p;//都不行

}

指针常量的声明符是 *const

char *const cp;

char const* pc;

const char* pc2;

后两个都是指向常量的指针,可以用*的位置来区分

可以将一个变量的地址赋给一个指向常量的指针,这样做是无害的,这样做可以防止对函数参数的修改。例如

char* strcpy(char* p,const char* q);//*q的值不会被修改

但是不能将一个常量的地址赋给一个指向变量的指针

void f()

{

int v=1;

const int v1=2;

const int* p1=&v;

const int* p2=&v1;

int* p3=&v1;//error;

*p3=7;

}

会有这样的一个错误

error C2440: 'initializing' : cannot convert from 'const int *' to 'int *'

10.

引用

一个引用就是一个对象的别名。

主要用途是传参和作为函数返回值,还有重载操作符。

X& 就表示对X的引用

void f(){

int i=1;

int &r=i;//r和i指向相同的int

int x=r;//x=1

r=2;//i=2

}

必须对引用进行初始化

int i=1;

int &r1=i;//ok

int &r2;//error

extern int &r3;//ok

一个引用在他初始化之后就不会再被改变了

引用不是一个对象,不能像操作指针一样操作

对一个普通的T&初始化必须是一个T类型的左值

对常量引用的初始化不必是一个左值,甚至不必是一个相应类型的

int &i=1;//error

const double &i1=1;//ok

第二种情况的解释可以是这样的

double temp=double(1);

const double &i1=temp;

这个temp将会一直存在,直到i1的作用域结束

一个引用可以用来指定函数的参数,这样函数就可以改变传入他的对象的值例如

void increment(int &a){a++;}

void f(){

int i=1;

increment(i);//i=2

}

为了提高程序的可读性,这样的写法应该尽量被避免

int next(int i){return i++;}

void incr(int* p){(*p)++;}

void f1(){

int x=1;

increment(x);//x=2

x=next(x);//x=3

incr(&x);//x=4

}

increment(x)并没有给读者一个x将要被改变的提示,如果要用引用,用一个带有强烈提示意味的名字

引用也可以用来定义一个函数,既可以作为左值,也可以作为右值,例子

struct Pair{

string name;

double val;

};

vector<Pair> pair;

double& value(string s)

{

for(int i=0;i<pair.size();i++)

if(pair[i].name==s) return pair[i].val;

Pair p={s,0};

pair.pushback(p);

return pair[pair.size()-1].val;

}

这样一个函数,我们就可以有很多用途,例如

string buffer;

while(cin>>buffer) value(buffer)++;

for(int i=0;i<pair.size();i++)

cout<<pair[i].name<<':'<<pair[i].val<<'/n';

这是结果

aa bb cc dd aa bb dd
^Z
^Z
aa:2
bb:2
cc:1
dd:2
Press any key to continue

11.

指向void的指针

一个指向任意对象的指针都可以赋值给void*,一个void*可以赋值给另一个void*,两个void*可以比较相等或者不相等,一个void*可以显式得转换为任意的类型,其他的操作会导致error。

void f(int* pi)

{

void* pv=pi;//ok,隐式得将int*转换为void*

*pv;//error

pv++;//error

int* pi2=static_cast<int*>(pv); // explicit conversion back to int*
double* pd1 =pv; // error
double* pd2 =pi; / / error
double* pd3 =static_ cast<double*>(pv) ; / / unsafe
}

void*最主要的用途是向函数传递一个未知类型的指针或者返回一个无类型的对象,要使用这样的对象,必须进行显示转换

void*一个用在底层代码中,例如,

void* my_alloc(size_t n)

12.

结构体

结构体是任意类型的一个集合。

struct address{
char* name; // "Jim Dandy"
long int number; // 61
char*street; // "South St"
char* town; // "New Providence"
char state[2]; // ’N’ ’J’
long zip; // 7974
};

注意最后的分号,别忘了写

用.来访问数据成员

可以用初始化数组的方式来初始化结构体

用指针通过->来访问数据成员

p->m相当于(*p).m

像其他类型一样,可以传参,返回值赋值等等,==和!=操作符不可用,可以自己定义。。

结构体在声明之后就可以使用,只要这个不使用不牵扯到结构体的成员变量和大小。

struct S ; // ‘S’ is the name of some type
extern S a ;
S f();
void g(S);
S* h(S*) ;

13.

结构体的内存对齐

字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。

一个结构体的大小不是所有类型的大小的和,因为存在一个对齐问题,vc6中,运行下面的测试程序

struct A{
int a;
short b;
bool c;
};
struct B{
short b;
int a;
bool c;
};
struct C{
bool c;
short b;
int a;
};
int main()
{
cout<<sizeof(A)<<'/n'
<<sizeof(B)<<'/n'
<<sizeof(C)<<'/n';
C tc;
cout<<&tc.c<<'/n'
<<&tc.b<<'/n'
<<&tc.a<<'/n';
}

结果是

8
12
8
0013FF78
0013FF7A
0013FF7C
Press any key to continue
以C结构体为例,

第一个元素c偏移量为0,bool的默认偏移量为1的倍数,可以,在0x0013ff78上

第二个元素b偏移量为1,short的默认偏移量为2的倍数,不行,填充一个,存在0x0013ff7a-0x0013ff7b上

第三个元素a偏移量为4,int的默认偏移量为4的倍数,可以,存在0x0013ff7b-0x0013ff7e上

VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。
最后的长度为8,是 4 的倍数,不需要填充。结果就是8

14.

类型等价

两个结构体总是不同的类型,即便它们的成员完全一样

每个结构体在程序里都是唯一定义的

练习

数组的引用

int a[10]={1,2,3};
int (&b)[10]=a;

字符串数组的指针

char* a[3]={"abc","def","ghi"};
char** p=a;

到字符指针的指针

char c='a';
char* p=&c;
char** pp=&p;

到常量整数的指针和到整数的常量指针

int A=100;
const int* pci=&A;
int *const cpi=&A;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: