2005年12月12日

突然感觉累了。很累……

好长时间了吧,自从找到工作以后就几乎在也没有看什么书

2个月的时间就这么让自己轻轻松松的放了过去,

每天睡到很晚很晚才起来,然后就开始了一天的网络之旅……

看了很多大片,却没有任何的收获,

这样的生活,累了……

不知道那天在华为面试的那位女士说得是不是很对,我是太争强好胜了,

这绝对不是什么好事,

人们往往都是很讨厌那种性格很犀利 很要强的人,

你为什么不能随和一点呢?

你知道你是爱大家的,你说呢?

为什么非要让一些无谓的事情在蚕食你的资本?

加油,即使你是争强好胜的。

 
枯燥的生活中 她也许是我得一些慰藉吧,虽然离的很远很远

2005年08月25日

那么什么时候数组&指针是相同的呢?
在c语言的标准中有如下说明:
1:表达式中的数组名被编译器当作一个指向该数组的第一个元素的指针。
2:下标总是于指针的偏移量相同。
3:在函数的声明中,数组名被编译器当作指向这个数组的第一个元素的指针。

对于第一点比如:
int a[10],*p,i=2;
有几种访问a[i]的方式,作用是一样的。
[1]:p=a;p[i];
[2]:p=a;*(p+i);
[3]:p=a+1;*p;

由此♂得出,对数组下标的引用总是可以写成“一个指向数组首地址的指针&偏

移量的和”

对于第三点的理由很简单,数组可能很大,如果作为形参也用传值的方式的话没

那么时间&空间上面来说是实在过不去的。

多维数组


♂知道一维数组的名字就是一个指针常量。那么多维数组也差不多。唯一的区别

就是,多位数组第一维实际上指向的是另外一个数组。

例如:
int max[3][4];
那么,max实际上是一个指向10个int元素的指针。
max+1就是指向max的下一行的10个元素。
*(max+1)也是一个指针,也许有点难理解,那么这么看吧.max[1]是什么?是一个

数组,那么这个数组名max【1】不就是一个指针么?数组名的值是个常量指针。

它指向数组的第一个元素。max[1]=*(max+1);
例子:
#include<iostream>

using namespace std;

int main()
{
    int a[3][2]={1,1,3,5,4,7};
    cout<<a<<"  "<<*(a+1)<<"  "<<&a<<"  "<<a[0]<<endl;
    system("pause");
    }

可以看出上面的观点。*(a+1)仍然是一个指针,指向当前行的首元素。
那么自然,*(max+1)+5;就是指向当前行的第6个元素了。

现在*(*(max+1)+5)才是对int的解引用,访问了那个整型变量。


指向数组的指针


int vector[10],*vp=vector;//合法,指针指向数组首元素。
int vector[3][10],*vp=vector;//不合法,在这里,vp是一个指向整型的指针,

但是vector却是一个指向整型数组的指针,在这里类型不匹配。
正确的声明应该是

int (*vp)[10]=vector;//按照在《怎么读一个很长的声明中》说得,这个应该读

成:vp是一个指向有10个整型元素的指针。ok 类型匹配了。这个时候vp指向

vector的第一行。

也许这不能满足你的要求,你希望有一个指针能遍历这个数组的所有元素,而不

是现在只能一行一行的取值。
那么这两个可以满足:
int *p=&vector[0][0];
int *p=vector[0];


作为函参数的多维数组

上面说过了,数组作为函数参数是作为指针传递的。多维数组也是一样。但是区

别在于多维数组的每个元素本身是另外一个数组,编译器需要知道它的维数。
例如:

int vector[3][4];
这个二维数组传递给函数的时候要这么写。
void fun(int (*p)[4]);
or
void fun(int p[][4]);

其中第二维是必须的,因为编译器需要知道这个维长度才能对下标求值。
关于二维数组传参常犯的错误就是
void fun(int **p);
看出来了么?这个指针根本没有任何关于维数的声明,所以数错误的。

                       --参考书籍《c&指针》《c专家编程》

2005年08月24日

数组&指针

          --令人吃惊的事实:数组&指针并不相同!    --《c专家编程》


1.数组名表示什么?
♂来看一个数组 int a[5];
a[3]表示的是一个整型,那么a呢?a表示什么?
在c中,数组名表示的是一个指针常量,表示一个“指向……的常量指针”。
但是,很重要的一点,数组&指针不是相同的!这个值是一个指针常量不是变量

常量的值是不能修改的
在两种情况下。数组名并不是用指针常量来表示
数组名作为sizeof操作符或者单目操作符&的操作数时。

♂看下面一个例子:
int a[10];
int b[10];
int *c ;
…..
c=&a[0];

表达式v&a[0]取的是数组第一个元素的位置,看上面♂说得,这其实也就是a所表

达的。
所以
c=&a[0];
和c=a;
是一样的。

a=c;//??这个是错的。因为a是常量,不能修改。记住,a是一个常量指针!

   数组&指针的区别
对于指针,它存放的数据的地址,但是数组存放的是数据本身。
指针是间接访问数据的,首先取得指针的内容,把它作为地址,然后从这个地址

取值,数组是直接访问数据的。
定义指针时,编译器不为指针指向的数据分配空间。只是给指针本身分配了。

2005年08月23日

在堆上分配10个证书构成的数组。
int *ip =new int(10);
……
for(int i=0;i<10;++i)
    ip[i]=i;
delete [] ip ;//使用[]是让编译器知道我们需要释放的ip指向数组。

遗憾的是 这样是错误的,实际上是ip指向一个用10初始化的一个整数,然而我们的代码在语法上是没有错误的。在某些编译器上面是可以通过的,在也许知道运行的时候♂才能发现。
正确的是

int*ip =new int [10];

这样可能分配不到空间,最好的办法是用vector

std::vector<int>ver(10);
for(int i=0;i<ver.size();++i)
    ver[i]=1;


求值顺序的不确定。—————– —————–

出处:CSDN

  对于初学者来说,内存是个神秘的空间。程序的绝大部分错误,也是在于内存的使用不当造成的,而且这些错误有些都是隐藏很深的。所以,如何掌握内存的使用,通晓系统对内存的管理手段,将是软件成功的一个非常关键的因素。
  
   首先我们要了解内存的分配方式。一般来说,内存的分配方式有三种:
  
   1.从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
  
   2.在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

  3.从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
  
   以上三种分配方式,我们要注意内存生命期的问题:
  
   1.静态分配的区域的生命期是整个软件运行期,就是说从软件运行开始到软件终止退出。只有软件终止运行后,这块内存才会被系统回收
  
   2.在栈中分配的空间的生命期与这个变量所在的函数和类相关。如果是函数中定义的局部变量,那么它的生命期就是函数被调用时,如果函数运行结束,那么这块内存就会被回收。如果是类中的成员变量,则它的生命期与类实例的生命期相同
  
   3.在堆上分配的内存,生命期是从调用new或者malloc开始,到调用delete或者free结束。如果不掉用delete或者free。则这块空间必须到软件运行结束后才能被系统回收。

  下面我们再看看,在使用内存的过程中,我们经常发生一些什么样的错误。以及我们应该采取哪些对策。
    
   发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。
  
   常见的内存错误及其对策如下:
  
   1 内存分配未成功,却使用了它。
  
   编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。
  
   2 内存分配虽然成功,但是尚未初始化就引用它。
  
   犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。
  
   内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。
  
   3 内存分配成功并且已经初始化,但操作越过了内存的边界。
  
   例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。
  
   4 忘记了释放内存,造成内存泄露。
  
   含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。
  
   动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)。
  
   5 释放了内存却继续使用它。
  
   有三种情况:
  
   (1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
  
   (2)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

  (3)使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

  综上所述,我们应该注意:
  
   1.用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。
  
   2.不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
  
   3.避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
  
   4.动态内存的申请与释放必须配对,防止内存泄漏。
  
   5.用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
  
   下面举几个经典的错误例子,大家不要犯同样的错误:
  
   1. 返回栈内存指针
  
   char *GetString(void)
  
  
  
   char* pGet = GetString();
  
   这段程序编译时没有错误,运行也没有错误,但是你却无法使得返回的pGet指针指向的数据是你想要的“hello world”,因为指针p的生命期是函数GetString内,运行完函数GetString后,p分配的栈空间马上被系统回收了。虽然pGet指向了p当初分配的内存地址,但是那块地址已经没有内容了。
  
   2.这是一个出现频率非常高的错误
  
   char* pChar = new char;
  
   ……
  
   int a ;
  
   pChar = &a;
  
   ……
  
   delete pChar;
  
   当然这是一个例子,具体的程序各有不同。
  
   这段程序有两个问题。一是pChar = &a;将导致pChar原先分配的空间无法再被获取,就象我们的丢失了朋友的电话号码一样,无法再联系这个朋友了。这就造成了内存泄漏。如果内存泄漏多了,可能导致系统的崩溃,因为可用的资源将越来越少,直到枯竭为止。第二个问题是delete pChar将导致异常发生,因为这时的pChar已经不是指向动态分配的内存了,而是指向了a分配的栈空间,而栈空间是不能使用delete来回收的,因此将导致内存异常。
  
   内存是财富,正确使用财富是关键,为人如此,编程也如此。

1-4 运算符的优先级和结合性

 

优先级

运算符

含义

要求运算对象的个数

结合方向

1

( )

[ ]

->

.

圆括号

下标运算符

指向结构体成员运算符

结构体成员运算符

 

自左至右

2

~

++

-

(类型)

*

&

sizeof

逻辑非运算符

按位取反运算符

自增运算符

自减运算符

负号运算符

类型转换运算符

指针运算符

地址运算符

长度运算符

1(单目运算符)

自右至左

3

*

/

%

乘法运算符

除法运算符

求余运算符

2(双目运算符)

自左至右

4

+

-

加法运算符

减法运算符

2(双目运算符)

自左至右

5

<<

>>

左移运算符

右移运算符

2(双目运算符)

自左至右

6

< <= . > >=

关系运算符

2(双目运算符)

自左至右

7

==

!=

等于运算符

不等于运算符

2(双目运算符)

自左至右

8

&

按位与运算符

2(双目运算符)

自左至右

9

^

按位异或运算符

2(双目运算符)

自左至右

10

|

按位或运算符

2(双目运算符)

自左至右

11

&&

逻辑与运算符

2(双目运算符)

自左至右

12

||

逻辑或运算符

2(双目运算符)

自左至右

13

? :

条件运算符

3(三目运算符)

自右向左

14

= += -= *= /= %=

>>= <<= &= ^= |=

赋值运算符

2(双目运算符)

自右向左

15

,

逗号运算符(顺序求值运算符)

 

自左至右

在c中优先级别的次序是这样的。
1. 声明中给括号包围起来的那部分。
2.后缀操作符。
3.前缀操作符。*表示“指向……的指针”
4.如果const&volatile关键字的后面紧跟类型说明符号(如int),那么它作用于类型说明符,在其他情况下左边紧接的类型指针。
              
              ---《c专家编程》


那么♂来看一个例子

char * const *(*next)();

首先♂来看括号里面的,看变量名next,根据上面第3点,♂可以认为:“next是一个指向……的指针”。
ok,我们继续。
接下来,后面是一个括号,那么♂可以这么读,“next是一个函数指针,指向一个返回……的函数”,很明白吧。
在后来,const前边有一个*,那么很清楚
这个声明可以理解为:“next是一个指针,指向一个函数,该函数返回一个指针,这个指针指向一个类型为char的常量指针。”(此处经过悠然自得得点拨,修改。)


好了,具体就这么多,

来个例子看看自己会了没有?答案在后面。

1.char*(*c[10])(int**p);
2.void(*signal(int sig,void(*func)(int)))(int);

对于1:c是一个数组,里面的元素类型是函数指针,这些函数返回的是一个指向char的指针,这些函数的参数是一个指向指针的指针。

对于2:signal是一个函数,返回一个函数指针。后者指向的函数接受一个int参数,返回void

2005年08月21日

char ch =’a';
char *cp=&ch;

我们来看&ch
作为右值,这个表达式是变量ch的地址。观察优先级表格♂知道&操作符′结果是个右值,不能作为左值使用。为什么呢?当表达式&ch求值时,他的结果存储于计算机的什么位置呢?这个时没有办法预知的。所以不能作为左值使用。

我们来看cp
作为右值,就是cp的值,,作为左值就是cp所在内存的位置。

接着♂看&cp
这次♂取的是指针变量的地址,这个结果的类型就是指向字符的指针的指针。同样 作为&的结果,不能作为左值使用。

ok,来看看*cp+1
通过优先级表格♂知道*的优先级要大于+,所以先执行接引用操作,*cp得到a在加1得到b。同&,+操作的结果也不能作为左值使用。(位置没有明确定义)

那么*(cp+1)是什么意思呢?
cp现在是指向a的一个指针,那么指针加1就指向a当前所在位置的下一个位置,然后对那个位置解引用,可以是左值,也可是右值。

来看一个奇妙的++cp
在这个表达式中我们增加了指针cp的值,表达式的结果是增值后的指针的一份拷贝,指向原先cp所指位置之后一个位置。由于这份拷贝的位置♂不知道所以没有办法作为左值使用。


理解了上面的,那么理解这个就容易多了 cp++
返回当前指针的一份拷贝,然后指针自加,同样不能作为左值使用。

*++cp
很容易理解,作为右值返回的是ch后面那个内存的值,左值就是位置本身。

*cp++
它的左值和右值分别是变量ch的地址&变量ch本身。
这里涉及3个步骤:首先,++操作符产生cp的一份拷贝。其次,++操作符增加cp的值。第三,在cp的拷贝上做间接访问操作。

++*cp
在这个表达式中,两个操作符的结合性都是自右到左(++优先级高于*),所以先执行*,然后cp指向的位置的值加1。

(*cp)++
这个和上面的上面的那个是不是很相似呢?由于++的优先级要高于*,所以在前边的那个♂先执行的是++,于是也就产生了那三个步骤。在这个表达式用,先执行的是*,然后对结果自加。

++*++cp
一个一个来分析,先执行了最右边的++,也就是产生了cp的一份拷贝指向ch后面的那个位置。然后对那个位置解引用,由*完成。最后对解引用的值自加。

++*cp++
先执行cp后面的++,产生cp的一份拷贝,cp指向ch之后。对这份拷贝执行解应用,然后加1。

指针可以作为左值,并不是因为他们是指针,而是因为他们是变量。
我们来看一个例子:
*d= 10-*d;//ok

d=10-*d;//???

*&a= 25;//

第一条语句包含了两个间接操作,右边的作为右值使用,所以它的值是d所指向的内存所存贮的值。左边的作为左值使用,所以更新了d所指向内存处的值。


对于第二条语句,♂说它是非法的。因为它把一个整型值存贮于一个指针变量中。


对于第三条语句,是把25赋值给变量a,首先&去取了a的地址,这是一个指针常量,
接着*访问这个位置,并且把25放在那个位置,也就是a的地址。你可以看出来这个表达式和
 a=25;//
是一样的,只不过是另外一种很“难看”的写法罢了。


来看一个有趣的话题:
*100 =25;对么?
我们说,间接访问操作符只能用在指针类型表达式中。而这里的 100 是一个整型值,所以,这个表达式是错误的。

如果♂想把25存在内存标号100的地方,那么我们必须要强制转换成:

*(int *)100=25;//ok

 

参考书目《c&指针》

指针是c、c++得灵魂……汗说这些干什么,言归正传。

一:初级话题
1.指针得声明&初始化

指针变量得值是内存地址,因此,可以说,变量名直接应用数值(directly),而指针间接引用数值(indirectly)。
指针和任何变量一样要先声明在使用。
int* ptr;//声明了一个指向int得指针。

指针应该在声明时或者赋值语句中初始化,可以初始化成0,NULL或者一地址。数值为0或者NULL得指针不指向任何内容,
但是c++中优先选择0,NULL时头文件中定义得符号化常量。

下面得代码说明了一个常见得问题
int * a;
….
*a=12;
这个声明创建了一个a指针变量,后面得赋值语句把12存储在a指向得内存位置,但是a指向什么的地方那?,我们没有办法
知道,这就是俗称得“野指针”,他可能指向一个你正在使用得位置,然后修改那个位置得值,但是你根本没有意愿去改变他。
这样得错误非常常见,但是也非常难以扑捉。

NUll指针作为一个特殊得指针变量表示不指向任何东西。
但是对一个NULL指针解引用会是什么情况那?
这是编译器相关得。有的会访问位置0点,这可能和你得想法是不一样得。所以得出一个重要得结论
**在对指针解引用前,必须明确它没有指向NULL!**