《C与指针》笔记

本文详细介绍了C语言中的指针,包括指针的重要性、自由形式的语法、预处理指令、函数原型、参数传递规则、NULL的使用、数组与指针的关系、函数指针的使用、回调函数的概念、泛型编程的void*指针应用、转移表和命令行参数处理。文章强调了指针在C语言中的核心地位和灵活运用。

1. 为什么学习C?

最主要是其效率。优秀的C程序效率几乎和汇编语言程序一样高。与其他语言相比,C 给予程序员更多的控制权,如控制数据的存储 位置和初始化过程等。C 缺乏 “安全网” 特性,这虽有助于提高它的效率,但也增加了出错 的可能性。例如,C 对数组下标引用和指针访 问并不进行有效性检查,这可以节省时间,。
C 提供了丰富的操作符集合,它们可以让程序员有效地执行一些底层的计算如移位和屏蔽等,而不必求助汇编语言。C 的这个特点使很多人把 C 称为 “高层” 的汇编语言。但是, 当需要的时候,C 程序可以很方便地提供汇编语言的接口。这些特性使 C 成为实现操作系统和嵌入性控制器软件的良好选择。
C流行的另一个原因是由于它的普遍存在 。C 编译器在许多机器上实现。另外,ANSI标准提高了 C 程序在不同机器之间的可移植性。最后,C 是 C十+的基础 。C++提供了一种和 C 不同的程序设计和实现的观点。然而,如 果你对 C 的知识和技巧,如指针和标准库等成竹在胸,将非常有助于你成为一名优秀的 C++ 程序员。

**  
--指针赋予C强大的威力 

--C是一种 自由形式的语言,也就是说并没有规则规定什么地方可以书写语旬,一行中可以出现,如:
y=x+1;可跨行写成:
y  =  x

1;

--
C  是一种大小写敏感的语言

**** 
2. 
# in clude < st r ing . h>
# def ine     MAX   COLS   2 0
这2 行称为预处理指令(preprocessor  directives),因为它们是 由预处理器(preprocessor)解释的
预处理器读入源代码,根据预处理指令对其进行修改,然后把修改过的源代码递交给编译器。

3.
int    readcolumnumbers(int);
这些声明被称为函数原型(function prototype)

4.关键字 void表示函数并不返回任何值,在其他语言里,这种无返回值的函数被称为过程(procedure)。

5.事实上,关于 C 函数的参数传递规则可以表述如下:
所有传递给函数的参数都是按值传递的。

6.符号 NULL  在头文件 stdio.h 中定义。另一方面,并不存在预定义的符号 NULL  

7.

下面这个表达式
(ch =get char())!= EOF&&ch!='\n'
值得花点时间讨论。首先,getchar 函数从标准输入读取一个字符并返回它的值。如果输入中不再存 在任何字符,函数就会返 回常量 EOF(在 stdio.h 中定义),用于提示文件的结尾。
如果 ch 等于 EOF ,整个表达式的值就为假 ,循环将终止.
补充:一个经常问到的问题是:为什么 ch 被声明为整型,而我们事实上需要它来读取字符?答案是 EOF 是一个整型值,它的位数比字符类型要多,把 ch 声明为整型可以防止从输入读取的字符意外 地被解释为 
EOF。但同时,这也意味着接收字符的 ch 必须足够大,足以容纳 EOF,这就是 ch 使用 整型值的原因。正如第 3 章所讨论的那样,字符只是小整型数而己,所以用一个整型变量容纳字符


8.
这些语句定义了 rearrange 函数并声明了一些局部变量。此处最有趣的一点是前两个参数被声明为指针 ,但在函数实际调用时,传给它们的参数却是数组名。当数组名作为实参时,传给函数的 实际上是→个指向数组起始位置的指针,也就是数组在内存中的地址。正因为实际传递的是一个指针而不是一份数组的拷贝,才使数组名作为参数时具备了传址调用的语义。函数可以按照操纵指针 的方式来操纵实参,也可以像使用数组名一样用下标来引用数组的元素。

9.putchar  函数,它与 getchar  函数相对应 ,它接受一个整型参数,并在标准输出中打印该字符(如前所述 ,字符在本质上也是整型)。

10. 精明的读者会注意到,如果遇到特别长的输入行,我们并没有办法防止 gets   函数溢出。这个漏洞确实是 gets   函数的缺陷
所以应该换用 fgets(将在第 15 章描述)。

11.在 C 语言中,仅有 4 种基本数据类型一一整型、浮点型、指针和聚合类型 (如数组和结构等)。所有其他的类型都是从这 4 种基本类型的某种组合派生而来

12.short int 至少 16 位,long int 至少 32 位。至于缺省的 int 究竟是 16 位还是 32 位,或者是其他值
则由编译器设计者决定。通常这个选择的缺省值是这种机器最为 自然 (高效) 的位数 

13.尽管设计 char 类型变量的目的是为了让它们容纳字符型值,但字符在本质上是小整型值。缺省
的 char 要么是 signed char ,要么是 unsigned char ,这取决于编译器。这个事实意味着不同机器上的 char 可能拥有不同范围的值。所以,只有当程序所使用的 char 
型变量的值位于 signed char 和 unsigned char     的交集中,这个程序才是可移植的。例如,ASCII      字符集中的宇符都是位于这个范围之 内的。


14.

注意 ,标准并没有规定长整型必须比短整型长,只是规定它不得比短整型短。ANSI    标准加入
了一个规范 ,说明了各种整型值的最小允许范围 , 

15.枚举(enlunerated)类型就是指它的值为符号常量而不是字面值的类型,
这种类型的变量实际上以整型的方式存储 ,这些符号名的实际值都是整型值

16.

int *  b ,  c ,  d ;
人们很 自然地以为这条语句把所有三个变量声 明为指向整型的指针,但事 实上并非如此。我们 被它的形 式愚弄了 。星号实际上是表达式吨 的一部分,只对这个标识符有用 。b 是一个指针,但其 余两个变量只是普通的整型。要声 明三个指针,正确的语句 如下:
int *b,*c ,*d ;

17.隐式声明
C 语言中有几种声明,它的类型名可以省略。例如,函数如果不显式地声明返回值的类型,它 就默认返回整型。当你使用旧风格声明函数的形式参数时,如果省略了参数的类型,编译器就会默 
认它们为整型。最后,如果编译器可以得到充足的信息,推断出一条语句实际上是一个声明时,如 果它缺少类型名,编译器会假定它为整型。
考虑这个程序:
int a;
b;  //默认是整型
c[10]; 
f(x){...} //默认函数返回整型


18.
你应该使用 typedef 而不是#define 来创建新的类型名 ,因为后者元法正确地处理 指针类型 。

#define  d_ptr_to_char char*
dptrtochar a,b;
正确地声 明了 a,但是 b 却被声 明为一个字符

19.链接属性


当组成  个程序的各个源文件分别被编译之后,所有的目标文件以及那些从一个或多个函数库中 引用的函数链接在一起,形成可执行程序。然而,如果相同的标识符出现在几个不同的源文件中时, 它们是像 Pascal  那样表示同一个实体?还是表示不同的实体?标识符的链接属性(linkage)决定如何处 理在不同文件中出现的标识符。标识符的作用域与它的链接属性有关,但这两个属性并不相同。链接属性一共有3种一一寸external (外部)、internal C 内部) 和 none (无)。没有链接属性的标识符(none) 总是被当作单独的个体,也就是说该标识符的多个声明被当作独立不同的实体。属于 internal  链接属性 的标识符在同一个源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的 
实体。最后,属于 external 链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体。

关键字 extern 和 static 用于在声明中修改标识符的链接属性。

20.尽管这个技巧在 C 程序中极为 常用 ,但它违背了软件工程的原则 。用一个单一的值表示两种不 同的意思是件危险的事,因为将来很容易无法弄清哪个才是它真正的用 意。在大型的程序中 ,这个 问题更为严 
重,因为你 不可能在头脑中对整个设计一览无余。一种更为安全的策略是让函数返回两 个独立的值:首先是个状态值,用 于提示查找是否成功;其次是个指针,当状态值提示查找成功时 它所指向的就是查找到的元素。
对指针进行解引用操作可以获得它所指向的值。但从定义上看,NULL 指针并未指向任何东西 因此,对 个 NULL 指针进行解引用操作是非法的。在对指针进行解引用操作之前,你首先必须确 保它并非 
NULL 指针 。

21. 
*&a =25;
这条语句和简单地使用 a=25;有什么区别吗?从功能上说,它们是相同的。但是,它涉及更多
的操作。除非编译器 (或优化器) 知道你在干什么并丢弃额外的操作,否则它所产生的目标代码将 会更大、更慢。更糟的是,这些额外的操作符会使源代码的可读性变差。基于这些原因,没人会故 意使用像*&a =25
这样的表达式。

22.
*100  =  2 5 ;
它看上去像是把 25 赋值给 a(地址100),因为 a 是位置 100 所存储的变量。但是,这是错的!这条语句实 际上是非法的,因为字面值 100 的类型是整型,而间接访 问操作只能作用于指针类型表达式。如果 
你确实想把 25 存储于位置 100,你必须使用强制类型转换。
*(int *) 100  = 25 ;

这个技巧唯一有用之处是你偶尔需要通过地址访问内存中某个特定的位置,它并不是用于访 问 某个变量,而是访 问硬件本身。例如,操作系统需要与输入输出设备控制器通信,启动 1/0 操作并 从前面的操作中获得结果。在有些机器上,与设备控制器的通信是通过在某个特定内存地址读取和 写入值来实现的。但是,与其说这些操作访 问的是内存,还不如说它们访 问的是设备控制器接 口。 这样,这些位置必须通过它们的地址来访  问,此时这些地址是预先已知的。

23.*操作符具有从右向左的结合性,
所 以这个表达式相当于*(*C),我们必须从里向外逐层求值。*c 访问c 所指向的位置

24.
char* cp=&ch;为什么这个表达式&ch不是一个合法的左值?优先级表格显示&操作符的结果是个右值,它不能当作左值使用。但是为什么呢?答案很简单,当表达式&ch 进行求值时,它的结 
果应该存储于计算机的什么地方呢?它肯定会位于某个地方,但你无法知道它位于何处。这个表达式并未标识任何机器内存的特定位置 ,所以它不是一个合法的左值。

*******************************
这个表达式的最终结果的存储位置并未清晰定义,所以它不是一个法的左值。优先级表格证实+的结果不能作为左值。*******************************

25.注意指针加法运算的结果是个右值,因为它的存储位置并 未清晰定义。如果没有间接访 问操作,这个表达式将不是一个合法的左值。然而,间接访 问跟随指 针访 问一个特定的位置 
。这样,*(cp+1)就可以作用左值使用,尽管cp+1本身并不是左值。间接访 问操作符是少数几个其结果为左值的操作符之一。

26.  *CP++
后缀++操作符的优先级高于*操作符, 但表达式的结果看上去像是先执行间接访问操作。事实上,这里涉及 3 个步骤:(1) ++操作符产生 cp 的一份拷贝,(2 )然后++操作符增加 cp 的值,( 3 )最后,在 cp 的拷贝上执行间接访问操作。

27. ++*CP
在这个表达式中,由于这两个操作符的结合性都是从右向左,所以首先执行的是间接访 问操作。 然后,cp 所指向的位置的值增加1,表达式的结果是这个增值后的值的一份拷贝。

28. 函数指针
int f(int);
int (*pf)(int)=&f;
第 2 个声 明创建了函数指针 肘,并把它初始化为指 向函数 f。函数指针的初始化也可以通过一
条赋值语句 未完成。在函数指针的初始化之前具有 f  的原型是很重要的,否则编译器就无法检查 f
的类型是否与 pf 所指向的类型一致。
初始化表达式中的&操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针 。
&操作符只是显式地说 明了编译器将隐式执行的任务。

int ans;
ans=f(2);
ans=(*pf)(22);
ans=pf(22);

第 1 条语句简单地使用名字调用函数 f,但它的执行过程可能和你想象的不太二样。函数名 f 首先被转换为)个函数指针 ,该指针指定函数在 内存中的位置 。然后,函数调用操作符调用该函数, 
执行开始于这个地址的代码。
第 2 条语句对 pf 执行间接访 问操作,它把函数指针转换为 →个函数名。这个转换并不是真正需 要的,因为编译器在执行函数调用操作符之前又会把它转换回去。不过,这条语句的效果和第 1条 
语句是完全一样的。
第 3 条语句和前两条语句的效果是 →样的。间接访 问操作并非必需,因为编译器需要的是二个 函数指针 。这个例子显示了函数指针通常是如何使用的。
什么时候我们应该使用函数指针呢?前面提到过,两个最常见的用途是把函数指针作为参数传 递给函数以及用于转换表。让我们各看 一个例子 。

29.
使用这种技巧的函数被称为回调函数callback  function,因为用户把一个函数指针作为参数传 递给其他函数,后者将 “回调” 用户的函数。任何时候,如果你所编写的函数必须能够在不同的时


30. C语言中泛型编程使用的是void*指针,其可作用于任何类型的值,表示“一个指向未知类型的指针”。

31.函数指针
你不会每天都使用函数指针。但是,它们确有用武之地,最常见的两个用途是转换表(jump    table)和作为参数传递给另一个函数。

32.回调函数---你可以创建通用型函数
使用这种技巧的函数被称为回调函数(callback  functio时,因为用户把一个函数指针作为参数传递给其他函数,后者将 “回调” 用户的函数。任何时候,如果你所编写的函数必须能够在不同的时刻执行不同类型的工作或者执行只能由函数调用者定义的工作,你都可以使用这个技巧 。许多窗口 系统使用回调函数连接多个动作,如拖拽鼠标和点击按钮来指定用户程序中的某个特定函数。

33.若函数的参数是void*时,比较具体值时,应转换:如, 
int compare(void const* a, void const* b){
   if(*(int *)a==*(int *)b).....
....
}


34.转移表---函数指针数组

转移表也使用函数指针 。转移表像 switch   吾句 样执行选择。转移表由二个函数指针数组组成
(这些函数必须具有相同的原型)。函数通过下标选择某个指针,再通过指针调用对应的函数。你必 须始终保证下标值处于适当的范围之 内,因为在转移表中调试错误是非常困难的。
例子:
int add(int,int);
int sub(int,int);...

int (*oper_func[])(int,int)={add,sub,...};
使用: res=oper_func[1](2,3); //sub(2,3);

弊端:在转换表中,越界下标引用 就像在其他任何数组中一样是不合法的。但一旦出现这种情况,把 它诊断出 来要困难得多 。当这种错误发生时 ,程序有可能在三个地方终止。首先,如采下标值远远越过了数组的边界,它所标识的位直可能在分配给该程序的内存之外。有些操作 系统能检测到这个 错误并终止程序 ,但有些操作 系统并不这样傲。如果程序被终止,这个错误将在靠近转换表语句 的 地方被报告,问题相对而 言较易诊断。


35.命令行参数--来自于dos界面输入的参数

$ cc -c -o main.c insert.c -o test
处理命令行参数是指向指针的指针的刃一个用武之地。
int main(int args, char**argv);
c 程序的 main  函数具有两个形参。第1个通常称为 argc ,它表示命令行参数的个数。
第 2 个通常称为 argv ,它指向一组参数值

36. 字符串常量出现于表达式中时,它的值是个指针常量(数组名也是指针常量)。
例子:
"xyz"+ 1    //结果为指针,指向第二个字符y
*"xyz"    //结果是:x
"xyz" [2]  //结果是z

//输出到屏幕16进制
remainder= value%16;
if(remainder=<10)
putchar(remainder+'0') ;
else
putchar( remainder-10+'A') 

//把二进制值转换为字符
putchar("0123456789ABCDEF"[value%16]);//

38.
如果声明得当,一个指针变量可 以指向另一个指针变量 。和其他的指针变量一样,一个指向指 针的指针在它使用之前必须进行初始化。为了取得 目标对象,必须对指针的指针执行双重的间接访 问操作。更多层的间接访 问也是允许的 ( 比如一个指向整型的指针的指针的指针〉,但它们与简单的指针相比用的较少。你也可以创建指向函数和数组的指针,还可以创建包含这类指针的数组。


39. 
宏非常频繁地用于执行简单的计算,比如在两个表达式中寻找其中较大 (或较小) 的一个:
为什么不用函数来完成这个任务呢?有两个原因。首先,用于调用和从函数返回的代码很可能 比实际执行这个小型计算工作的代码更大,所以使用宏比使用函数在程序的规模和速度方面都更胜 一筹。


和使用函数相比,使用宏的不利之处在于每次使用宏时,一份宏定义代码的拷贝都将插入到程
序中。除非宏非常短,否则使用宏可能会大幅度增力日程序的长度。 还有一些任务根本无法用函数实现。让我们仔细观察定义于程序 11.la  的宏 。这个宏的第 2 个参数是二种类型,它无法作为函数参数进行传递 。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Poo_Chai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值