《Pointers on C》备忘

三种链接属性:externalinternalnone。属于internal链接属性的标识符在同一个源文件内的所有声明中都指同一个实体;位于不同源文件的多个声明则分属不同的实体。属于external链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体。

 

凡是在任何代码块之外声明的变量总是存储于静态内存之中……对于在代码块内部声明的变量,如果给它加上关键字static ,可以使它的存储类型从自动变为静态……注意,修改变量的存储类型并不表示修改该变量的作用域,它仍然只能在该代码块内部按名字访问。函数的形参不能声明为静态,因为实参总是在堆栈中传递给函数,用于支持递归。

 

对于有符号值,到底是采用逻辑移位还是算术移位取决于编译器……一个程序如果使用了有符号数的右移位操作,它就是不可移植的。

 

任何一个值在内存里都只是一个01的序列,比如1078523331,在内存中是????....??? ,它既可以声明成整数1078523331也可以声明成浮点数3.14,也可以声明成其它数据类型的值。

 

在绝大多数表达式中,数组名的值是指向数组第1个元素的指针。只有在两种场合下,数组名并不用指针常量来表示--就是当数组名作为sizeof操作符或单目操作符&的操作数时。

 

C的下标引用和间接访问表达式是一样的!下标引用实际上只是间接访问表达式的一种伪装形式!

 

数组名作为函数参数并没有违背传值调用原则,因为数组名本身是个指针常量,传递给函数是这个指针的一个拷贝!因此C语言的函数参数传递只有传值调用一种!

 

int const * piint * const pi

关键看const前面是什么,前面是什么说明什么不能改,剩下的那个可以改;如int * const piconst前面是*,说明指针不能改而指针指向的内容可以改

 

当数组的初始化局部于一个函数(或代码块)时,你应该仔细考虑一下,在程序的执行流每次进入该函数时,每次都对数组进行重新初始化是不是值得。如果答案是否定的,你就把数组声明为static,这样数组的初始化只需在程序开始前执行一次。(涉及C中变量初始化的原理)

 

有如下声明:

int matrix [3] [10] ;

表达式matrix 的类型是指向包含10个整型元素的数组的指针,它的值是3个子数组里面第1个子数组

matrix + 1 也是指向数组的指针,它的值是第2个子数组

* ( matrix + 1 ) 是个整型指针,指向第二个子数组的第一个元素

* ( matrix + 1 ) + 5 是个整型指针,它指向的位置比上个表达向后移动了5个单位[1]

* ( * ( matrix + 1 ) + 5 ) 就很简单了,实际上可以把 * ( matrix + 1 ) 改写成 matrix[1] ,把这个下标表达式代入原来的表达式,我们将得到 * ( matrix[1] + 5 ) matrix[1] 选定一个子数组,所以它的类型是一个指向整型的指针。我们可以再次用下标代替间接访问,所以这个表达式还可以写成matrix[1][5]

声明一个指向数组的指针可以:

       int ( * p ) [10] = matrix ;

它使p指向matrix的第一行。

如果你需要一个指针逐个访问整型元素而不是逐行在数组中移动,可以:

       int * pi = & matrix [0][0];

       int * pi = matrix[0];

matrix 作为函数的参数,函数的原型可以使用下面两种形式中的任何一种:

      void func ( int (* mat)[10] );

       void func ( int mat[ ][10] );

注意:void func ( int **mat ); 是不对的。

 

 

在某个源文件中声明:

       int a[10];

       int *b=a;

在另一个不同的源文件中,发现了

       extern int *a;

       extern int b[];

       int x,y;

       ……

       x=a[3];

       y=b[3];

那么这两个赋值一定都是错的。原因自己能分析出来。

 

 

       NULNULL不一样,前者是个位模式全为0的字节,用作字符串的结尾,但它身并不是字符串的一部分,所以字符串的长度并不包括NUL字节。

      

 

       结构体 Ex x={10,”Hi”,{5,(-1,25)},0};

                 Ex *px=&x;

       我们比较一下表达式*pxpx->a。在这两个表达式中,px所保存的地址都用于寻找这个结构。但结构的第1个成员是a,所以a的地址和结构的地址是一样的。这样px看上去是指向整个结构,同时指向结构的第1个成员:毕竟,它们具有相同的地址。但是,这个分析只有一半是正确的。尽管两个地址的值是相等的,但它们的类型不同。变量px被声明为一个指向结构的指针,所以表达式*px的结果是整个结构,而不是它的第1个成员。

 

       编译器为一个结构变量的成员分配内存时要满足它们的边界对齐要求。在实现结构存储的边界对齐时,可能会浪费一部分内存空间。sizeof操作符能够得出一个结构的整体长度,包括因边界对齐而浪费的字节空间。

 

       向函数传递一个指向结构的指针,其效率几乎总是比传递结构本身要高。在结构指针参数的声明中可以加上const关键字防止函数修改指针所指向的结构。

 

       如果你拥有一个指向结构的指针,你该如何访问这个结构的成员呢?首先就是对指针执行间接访问操作,这使你获得这个结构。然后你使用点操作符来访问它的成员。但是,点操作符的优先级高于间接访问操作符,所以你必须在表达式中使用括号,确保间接访问首先执行。举个例子,假定一个函数的参数是个指向结构的指针,如下面的原型所示:

       void func ( struct COMPLEX * cp );

       函数可以使用下面这个表达式来访问这个变量所指向的结构的成员f

       ( * cp ) . f

       对指针执行间接访问将访问结构,然后点操作符访问一个成员。

       由于这个概念有点惹人厌,所以C语言提供了一个更为方便的操作符来完成这项工作-à。和点操作符一样,箭头操作符接受两个操作数,但左操作数必须是一个指向结构的指针。箭头操作符对左操作数执行间接访问取得指针所指向的结构,然后和点操作符一样,根据右操作数选择一个指定的结构成员。但是,间接访问操作内建于箭头操作符中,所以我们不需要显式地执行间接访问或使用括号。

 

 

       单链表和双链表:本书所提倡的单链表是光头单链表,注意传给处理函数的应该是表头元素的指针的指针;双链表是有一个表头节点的,甚至可以为表头节点单独定义一种只包含指针不包含值的结构类型。

 

       看几个声明:

       int f ( )[ ];

       f是一个函数,它的返回值是一个整型数组。非法!函数只能返回标量,不能返回数组

       int f [ ]( );

       f是一个数组,其元素是返回值为整型的函数。非法!数组的元素必须具有相同长度。  int ( * f [ ] )( );

       合法。f是数组,数组元素类型是函数指针,它所指向的函数返回值是整型。

       int * ( * f [ ] )( );

       f是一个数组,数组元素是函数指针,它所指向的函数返回值是整型指针。

       弄清这些复杂声明的关键是搞清操作符的优先级,确定表达式求值的顺序! 

 

       下面代码段说明了一种初始化函数指针的方法:

       int f ( int ) ;

       int ( * pf )( int ) = & f ;

       表达式中的 & 操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针!& 操作符只是显式地说明了编译器将隐式执行的任务(跟数组名是一样的)

       下面三种调用函数的方式都是正确的:

       in tans ;

       ans = f ( 25 ) ;

       ans = ( * pf )( 25 ) ;

       ans = pf ( 25 ) ;

       第一条语句简单地使用名字调用函数f,但实际上函数名f首先被转换为一个函数指针,指定函数在内存中的位置,然后函数调用操作符调用该函数,执行开始于这个地址的代码。

       2条语句对pf执行间接访问操作,它把函数指针转换为一个函数名。这个转换是多此一举的!因为编译器在得到函数名之后会去执行第1条语句的过程

       3条语句和前两条的效果是一样的,因为编译器需要的就是一个函数指针。

 

 

       一个吃惊的事实:当字符串常量出现于表达式中时,它的值是个指针常量!

       下面这个表达式:

       “ xyz “ + 1 结果是个指针,指向y

“ xyz “  结果是x

“ xyz “ [2]  结果是z

* ( “ xyz “ + 4 ) 错了,下标越界,呵呵

 

 

       #include指令使另一个文件的内容被编译。这种替换执行的方式很简单:预处理器删除这条指令,并用包含文件的内容取而代之。这样,一个头文件如果被包含到10个源文件中,它实际上被编译了10次。

 

 

       在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

1.       在调用宏时,首先对参数进行检查,看看是否包含了任何由#define定义的符号。如果是,它们首先被替换。

2.       替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被它们的值所替代。

3.       最后,再次对结果文本进行扫描,看看它是否包含了任何由#define定义的符号。如果是,就重复上述处理过程。

这样,宏参数和#define定义可以包含其他#define定义的符号。但是,宏不可以出现递归。

当预处理器搜索#define定义的符号时,字符串常量的内容并不进行检查。你如果想把宏参数插入到字符串常量中,可以使用两种技巧。

 

 

       ……当一个流被打开之后,它可以用于输入和输出,它最简单的形式是字符I/0,字符输入是由getchar函数家族执行的,它们的原型如下:

       int fgetc ( FILE *stream );

       int getc ( FILE *stream );

       int getchar (void );

       需要操作的流作为参数传递给getcfgetc,但getchar始终从标准输入读取。每个函数从流中读取下一个字符,并把它作为函数的返回值返回。如果流中不存在更多的字符,函数就返回演常量EOF

       这些函数都用于读取字符,但它们都返回一个int型值而不是char型值。尽量表示字符的代码本身是小整型,但返回int型值的真正原因是为了允许函数报告文件的末尾(EOF) 。如果返回值是char型,那么在256个字符中必须有一个被指定用于表示EOF。如果这个字符出现在文件内部(可想而知)……   

       让函数返回一个int型值就能解决这个问题。EOF被定义为一个整型,它的值在任何可能出现的字符范围之外。

       int ungetc ( int character , FILE *stream ); 把一个先前读入的字符返回到流中,这样它可以在以后被重新读入。下面是一个经典用法:

       /*

       **把一串从标准输入读取的数字转换为整数

       */

       #include <stdio.h>

       #include <ctype.h>

       int read_int ( ){

              int value ;

              int ch ;

              value = 0 ;

              while ( ( ch = getchar ( ) ) ! = EOF && isdigit ( ch ) ) {

                     value * = 10 ;

                     value + = ch – ‘ 0’ ;

              }

 

              /*

              **把非数字字符退回到流中,这样它就不会丢失

              */

              ungetc ( ch , stdin ) ;

              return value ;

       }

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值