66序:
最大收获 |
无论函数形式怎么写。万变不离其宗。。大致8种(返回值,指针否,数组否);还有非法的函数写法
或者 记住最复杂的类型(其他功能都是他的简化) int *(*f[])() |
| 什么时候开始使用回调函数 什么是 转移表 () |
最大收获 | 复杂声明的处理方法: (大道至简) |
| 编写与类型无关的链表查找代码--函数指针用途之一 (测试用例) |
| 指针的指针。。。命令参数 |
| 字符串常量指针 |
|
|
引:主要内容来自网上的学习笔记。在此感谢。
本章的东西比较少,主要是关于指针的高级应用.对于指针的应用有一点就必须要注意了.运算符的顺序,比如怎么写是指针数组,怎么写是指向数组的指针.
1.int *f();//一个函数,返回值是int *
int (*f)();//一个函数指针,指向一个返回值是int的函数.注意这里我省略了参数
int *(*f)();//一个函数指针,指向的函数的返回值是int *
对于这些可以这么理解,int (*f)();把*f看成一个整体,是个函数,然后*f就变成了指向这个整体的一个指针.
int *(*f[])();//f是一个数组,数组的每个元素是一个指针类型,指针指向返回值是int *的函数
在Unix系统中,可以用cdecl程序来帮助你解释这些声明.
2.函数指针的两个应用:
I.回调函数,大致模板如下:int fun(int a,int (*com)(void const *a,void const *b));这里的com就是一个回调函数,也就是用户需要用fun函数的时候,需要传一个函数的指针过来,而指向的这个函数是由用户来编写的.这里可以参照qsort的cmp函数.
什么时候用回调函数:编写的函数必须能够在不同的时候执行不同类型的工作或者执行只能由函数调用者定义的工作,你都可以使用这个技巧。
注意:回调函数传过来的是一个函数指针,而不是函数本身.这个函数参数必须是void const *.在函数里面你必须保证转换成正确的类型.如果想和系统的一些函数保持兼容的话,那么相等返回0.不相等返回1.这个主要好似字符串的时候,这样可以表示3种情况.0:相等,-1:第一个小.1(或者只是一个大于0的书):第一个大.
II.转移表:就是一个数组的每个元素都是函数指针.然后通过下标来访问对应的函数.double (*oper_func [])(double,double)={add,sub,mul,div};
转移表要非常注意下标的溢出,一点溢出就可能非常难于调试.
3.main函数的两个参数.这个没什么讲的,argc表示参数个数,argv是参数指针.argv的第一个是文件名.
4.字符串常量,是一个指针常量.”abc”[2]的值是’c’.这里的[2]可以看成是指针+2.这样就很好理解了,下面的函数打印出的’*'随n的不同而不同
void mystery(int n)
{//n有范围限制
n += 5;
n /= 10;
printf(“%s\n”,”**********”+10-n);
}
本章编程提示总结:
1.如果并非必要,避免使用多层间接访问
2.cdecl程序可以帮助你分析复杂的声明
3.把void *强制转换为其他类型的指针时必须小心
4.使用转移表时,应该始终验证下标的有效性.[可以在调用函数的开始和结束输出一些有意义的话语]
5.破坏性的命令行参数处理方式使你以后无法再次进行处理
6.不寻常的代码始终应该加上一条注释,描述它的目的和原理
源文档 <http://www.fengshuxin.com/pointers_on_c_advanced_point.html>
附上:复杂声明的分解方法:
分析C语言中的高级声明时,记住三点:
1、用于声明变量的表达式和普通的表达式在求值时所使用的规则相同
2、下标运算符[ ] 和( )的优先级大于*
3、从里往外分解,可以使用替代符号帮助分解
第13章 高级指针话题
1. 高级指针
1.) int *f() // 由于()优先级高于*,f是一个函数,函数 | 返回一个指向整型的指针; |
2.) int (*f) () // 第二个圆括号表示函数,第一个圆括号,是聚组的作用,这里f是指向函数返回值的指针,函数返回一个整形值;
3.) int *(*f) () // (*f)部分和上面表达式2.)作用是一样的,它是个指向函数返回值的指针,但这里的函数 | 返回的是整形指针。 |
4.) int f() [] // 首先f()代表是个函数,返回值是个数组。但是函数只能返回标量值,而不能返回数组。所以此表达式是非法的。
5.) int f[] () // f[] 是个数组,元素类型是函数。由于数组元素的长度必须一致,而不同的函数可能长度不同,所以此表达式也是非法的。
6.) int (*f[]) () // 根据左右法则,先找到f[],它是个数组,再看左边的*,去与(*f[])外面的()匹配,得出f是个数组的元素是指向函数返回值的指针。
7.) int *(*f[]) () // 此表达式和上面的6.)的区别是多了个间接访问,所以f是个指针数组,元素是指向函数返回值的指针。
结合上面的表达式后,就不难看出下面两个表达式代表的意义了:
8.) int (*f) (int ,float) // f是一个指向函数返回值的指针,函数有两个参数
9.) int *(*g[]) (int, float) // g是一个数组,数组元素为指向函数返回值的指针
上面两个函数原型是ANSI C风格,尽管增加了函数声明的复杂度,但仍应提倡这种风格,因为它向编译器提供了一些额外的信息。
66 | 以出题人的角度: |
| Fun , *fun ,*fun[], |
| 返回值类型是最好判断的 返回是否是指针 * (f不是数组 ,是否f指向返回值是指针2) *(f是数组元素) , 大致分类就是 2*2*2大类而已。 |
| 注意 2种 非法的指针写法 |
2. 函数指针
1.) 初始化
int f(int); // 函数指针初始化前具有f的原型是必须的,否则编译器就无法检查f函数类型是否与pf所指向的类型一致
int (*pf) (int) = &f; // | &是可选的,因为器会将函数名转换为函数指针,&操作符只是显式的说明了编译器将隐式执行的任务 |
2.) 编写与类型无关的链表查找代码,解决方案是使用函数指针。
调用函数指针的方法是,调
用者编写一个函数,用于比较两个值,再把一个指向这个函数的指针作为参数传递给查找函数。然后,查找函数调用这个比较函数来执行值的比较。使用这种方法,任何类型的值都可以进行比较。代码实现如下:
a#include <stdio.h>
#include <string.h>
#include "node.h"
// 查找函数,第一个参数是指向链表第一个节点的指针,第二个参数是一个指向我们要查找的值的指针,第三个参数是比较函数的指针
Node * search_list(Node *node, void const *value, int (*compare) (void const*, void const*))
{
while(node != NULL)
{
if (compare(&node->value, value) == 0) // 如果找到
break;
node = node->link;
}
return node;
}
// 比较函数,参数必须声明为void *以匹配查找函数的原型,再强制转换为int *类型,用于比较整形值
int compare_ints(void const *a, void const *b)
{
if (*(int *)a == *(int *)b)
return 0; // 如果找到
else
return 1;
}
// 下面是函数的调用
desired_node = search_list(root, "desired_value", compare_ints);
2012-3-4 11:37:37 | 66:不过回调而已 |
| 不过此轮品读: 类型无关,具体函数需要转换指针哈 |
3. 命令行参数,处理命令行参数是指向指针的另一个用武之地
1.) 传递命令行参数
main函数有两个形参,第1个称为argc,它表示命令行参数的数目;第2个称为argv,它指向一组参数值。由于参数的数目并没有内在的限制,所以arv指向这组参数值的第一个元素。这些元素的每个都是指向一个参数文本的指针。如果程序要访问命令行参数,main函数在声明时就要加上以下参数:
2012-3-4 11:38:42 | 66:想到cdecl 是调用恢复堆栈,用于不定参数 |
int main(int argc, char **argv) // argc与argv的名字可以是其它的
int main(int argc, char **argv)
{
while(*++argv != NULL)
printf(“%s\n”, *argv); // %s要求参数是一个指向字符的指针
return EXIT_SUCCESS;
}
2.) 处理命令行参数
命令行每个参数可能包含多个选项,可以用一个循环来处理:
while ((opt = *++*argv) != ‘\0’) | 66:品味短码 |
{
switch(opt)
{
case ‘a’:
option_a = TRUE;
break;
}
}
4. 字符串常量指针
1.) “xyz” + 1 // 字符串常量实际上是个指针,表达式计算“指针值加1”的值,结果是个指针,它指向字符串中的第2个字符:y
2.) *“xyz” // 字符串常量类型是“指向字符的指针”,对指针执行间接访问结果是指针所指向的内容,表达式结果是它的第一个字符:x
3.) “xyz”[2] // 表达式结果是:z
4.) *(“xyz” + 4) // 表达式越界,出现非法访问
2012-3-4 11:41:30 | 这种用法暂时还没用过 |
66序: | 预处理过程 inclue #if #define #pragma ; |
| 库中的宏 |
| 预处理副作用 (),与getchar,与空格 |
| 你可以在所有的#include 语句中使用双引号而不是尖括号。 |
| 预处理 与函数 对比 (4-5点) |
Time | 2012年3月4日11:18:47 |
第14章 预处理器
C程序处理的第一步被称为预处器(preprocessing)阶段,C预处理器在源代码编译前对其进行一些文本性质的操作。
主要任务包括删除注释,
插入被#include指令包含的文件的内容,
定义和替换由#define指令定义的符号
以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。
1. 预定义符号如下:
符号 样例值 含义
__FILE__ "name.c" 进行编译的源文件名
__LINE__ "25" 文件当前行的行号
__DATE__ "Jan 21 1997" 文件被编译的日期
__TIME__ "18:04:30" 文件被编译的时间
__STDC__ 1 如果编译器遵循ANSI C,其值就为1,否则未定义
注:__是两个连续的下划线
2. #define
使用#define可以把任何文本替换到程序中,如:
#define reg register
#define do_forever for (; ;) // 用符合来代替实现无限循环的for语句
#define CASE break; case // 自动把每个break放在每个case之前
如果定义的内容非常长,可以分成几行,并在每行的末尾都要加一个反斜杠,如:
#define DEBUG_PRINT printf(“File %s line %d:” \
“x = %d, y= %d, z = %d, \
__FILE__, __LINE__, \
x, y, z)
3. 宏
#define 允许把参数替换到文本中,通常把这种称为宏(macro)或定义宏,下面是宏的声明方式:
#define name(parameter-list) stuff
parameter-list(参数列表)是一个由逗号分隔的符号列表,它们可能出现在stuff中,参数列表的左括号必须与name紧邻,如果两者有任何空白存在,参数列表就会被解释为stuff的一部分。
#define SQUARE(x) | x * x |
不过上面的代码有个副作用,就是当x是一个表达式时,结果不是预期的,如:
SQUARE(5 + 1) // 实际替换为 5 + 1 * 5 + 1,结果为11
解决这一问题,只要将stuff部分的x用括号括起来就可以:
SQUARE(x) (x) * (x)
再有另外一有宏定义:
#define DOUBLE(x) (x) + (x)
int a = 5;
printf(“%d”, 10 * DOUBLE (a)); // 10 * DOUBLE(a) 实际替换为 10 * 5 + 5,结果为55
而解决这个问题只要在整个表达式的两边加上一对括号就可以了:
#define DOUBLE(x) ((x) + (x))
其实,所以有数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时,由于参数中的操作符或邻近的操作符之间不可预料的相互作用。
2012-3-4 11:04:492
66 多括号,无空格
4. 宏与函数
宏经常用于执行简单的计算,如求两个表达式中的最大值:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
不用函数而用宏的原因有两个:一是,定义和调用的函数代码要比宏的大,所以使用宏比使用函数在程序的规模和速度上都更胜一筹;二是,函数参数必须声明为一种特定的类型,所以它只能在类型合适的表达式上使用,而宏与类型无关,这也是最为重要的一点。
2012年3月4日11:06:23 | 66:没有调用开销,少参数类型检查 |
但和函数相比,宏也有不利之处,每次使用宏时,一个宏定义代码的拷贝都将插入到程序中。除非宏非常短,否则使用宏会大幅度增加程序的长度。
5. 带副作用的宏参数
当宏参数在宏定义中出现的次数超过一次时,如果这个参数具有副作用,那么当你使用这个宏时就可能出现危险,导致不可预料的结果。如:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
z = MAX(x++, y++); // 将替换为:((x++) > (y++) ? (x ++) : (y++)),这个表达式,较小的值只增值了一次,但较大的却增值了两次,第一次是在比较时,第二次是在执行?后面的表达式出现。
另外,getchar()作宏参数时,也有副作用。调用这个函数将“消耗”输入的一个字符,如果用户意图不想“消耗”输入的字符,就不能重复调用这个函数。
2012-3-4 11:08:11 | 不得在 宏中重复调用getchar(),否则会消耗字符 |
6. 命名约定,宏名字全部大写
7. 宏和函数的区别:
属性 #define宏 函数
代码长度 每次使用时,宏代码都被插入到程序中,除了非常小的宏之外,程序的长度将大幅增长 函数代码只出现在一个地方,每次使用这个函数时,都调用那个地方的同一份代码
执行速度 更快 存在函数调用/返回的额外开销
操作符优级 宏参数的求值是在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能会产生不可预料的结果 函数参数只在函数调用时求值一次,它的结果值传递给函数。表达式的求值结果更容易预测
参数求值 参数每次用于宏定义时,它们都将重新求值。由于多次求值 ,具有副作用的参数可能会产生不可预料的结果 参数在函数被调用前只求值一次。在函数中多次使用参数并不会导致多种求值过程,参数的副作用并不会造成任何特殊的问题
参数类型 宏与类型无关。只要对参数的操作合法,它可以使用任何参数类型 函数的参数与类型有关,如果参数类型不同,就需要使用不同的函数,即使它们执行任务是相同的
2012-3-4 11:13:00 | 这里可以反复评味: 参数类型,参数副作用,优先级?, 长度 |
8. #undef 用于移除一个宏定义。如果出现已存在的宏名字要被重新定义,就要用#undef移除此前的旧定义
9. 条件编译(conditional compilation)是指,你可以选择代码的一部分是正常编译还是完全忽略。用于支持条件编译的基本结构是#if指令和与其匹配的#endif指令。语法结构为:
#if constant-expression
statements
#endif
另外,#if指令还具体可先的#elif和#else子句,语法结构为:
#if constant-expression
statements
#elif constant-expression
other statements
#else
other staements
#endif
上面的#elif子句出现的次数是不限的,每个constant-expression(常量表达式)只有当前面所有常量表达式的值为假时才会被编译。
10. 测试符号是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
每对定义的两条语句是等价的,但#if形式功能更强。
11. 文件包含
1.) 函数库文件的包含,格式:
#include <filename>
2.) 本地文件的包含,格式:
#include “filename” // filename可以是包含路径的文件名
处理本地文件包含时,通常在源文件当前所在的目录查找,如果该头文件未找到,编译器就按查找函数库文件一样在标准位置查找本地头文件。所以,你可以在所有的#include 语句中使用双引号而不是尖括号。但对有些编译器来说,在查找函数库头文件时,可能会浪费少许时间。
2012-3-4 11:14:35 心得 | 看来 双引号作用域 大于<> |
12. 嵌套文件包含
在大型程序中,可能出现多重包含的情况,如:
#include “a.h”
#include “b.h”
但a.h和b.h两个头文件中都包含一个嵌套的x.h头文件,那么就出现了多重包含。要解决这个问题,可以使用条件编译:
#ifndef _HEADERNAME_H
#define _HEADERNAME_H 1 // 当头文件第一次被包含时,被正常处理,如果头文件再次包含,此时符合_HEADERNAME_H被定义为1,内容就会被忽略
…
#endif
但即使这个文件所有内容被忽略,但会手慢编译速度,如果可能,应避免多重包含。
源文档 <http://www.douban.com/group/topic/16634614/>