关于《C和指针》的学习笔记

  有了之前的基础,此文只是把一些以前没有注意到的和值得学习的知识做一个记录。

第一章

  作者认为使用#if 0 .... #endif比用/*和*/好,因为后者不能嵌套。但是对于//并没有说明。

第二章  

  三字母词,用两个问号加一个符号表示另一个符号,比较类似于转义字符。查阅了一些资料,它的使用与编译器有关,了解即可,防止字符串常量被错误的解释。


代码如下:

??( ==> [   ??< ==> {   ??= ==> #

??) ==> ]   ??> ==> }   ??/ ==> \

??! ==> |   ??' ==> ^    ??- ==> ~

  对于嵌套较深的函数,作者建议把它分成几个函数来实现,不至于使用Tab缩进过多。

第三章  数据

  对于static的复杂用法,当它用于函数定义或代码块之外的变量声明时,static用于修改标识符的连接属性,从external改为internal,但标识符的存储类型和作用域不受影响。用这种方式声明的函数或变量只能在声明它们的源文件中访问。当它用于代码块内部的变量声明时,static用于修改变量的存储类型,从自动变量修改为静态变量,但变量的链接属性和作用域不受影响。用这种方式声明的变量在程序执行之前创建,并在程序的整个执行期间一直存在,而不是每次在代码块开始执行时创建,在代码块执行完毕后销毁。

第五章  操作符和表达式

  移位操作,当移动的位数为负值时,具体的结果与编译器有关或者是未定义的,比如a<<-5可能是左移27位。

  形如a+=1的操作效率比a=a+1高,等价的a[ 2 * (y - 6*f(x)) ]=a[ 2 * (y- 6*f(x))] + 1与a[ 2 * (y- 6*f(x))] += 1相比,后者不用重复计算下标。

  sizeof x的形式是允许的。sizeof()并不对表达式求值,因此sizeof(a=b+1)中的a没有赋值。

  访问指向结构的指针的成员时只用->。

第六章  指针

  未初始化的指针会导致错误。int *a; *a = 12,这使a指向的地址的内容被修改,结果是无法预料的。

  作者认为在诸如搜索元素而未找到时返回值为NULL指针虽然是C的常用技巧,但违背了软件工程的原则:“用一个单一的值表示两种不同的意思是件危险的事,因为将来很容易无法弄清哪个才是它的真正用意”。安全的策略是返回两个值,表示是否成功的状态值和查找成功时所查找到的元素值。

第七章  函数

  无参数的函数原型声明应该写作这样:int func(void); 目的是不与旧式风格声明混淆。

  递归解决问题比非递归更为清晰,对于一个复杂问题,难以用迭代形式实现时,递归实现的简洁性可以补偿它所带来的开销。Fibonacci是一个常见的递归的例子,但冗余计算很多,开销太大,实际上并不如迭代实现。


代码如下:

Fibonacci的迭代实现

long fibonacci(int n)
{
    long result;
    long previous_result;
    long next_older_result;

result = previous_result = 1;

while( n > 2) {
       n -= 1;
       next_older_result = previous_result;
       previous_result = result;
       result = previous_result + next_older_result;
     }
    return result;
}

  可变参数列表的使用:头文件stdarg.h,其中声明了一个类型va_list和三个宏va_start、va_arg、va_end。通过声明va_list类型的变量与这几个宏配合使用,访问参数的值。函数声明了一个var_arg的变量用于访问参数列表未确定部分,它通过va_start初始化。第1个参数是va_list变量的名字,第2个参数是省略号前最后一个有名字的参数。初始化过程把var_arg变量指向可变参数部分第1个参数。va_arg接受两个参数:va_list和参数列表的下一个参数的类型。va_arg返回参数的值并使var_arg指向下一个可变参数。访问完毕调用va_end。


代码如下:

#include <stdarg.h>

float average( int n_values, ... )
{
    va_list var_arg;
    int count;
    float sum = 0;

/*准备访问可变参数*/
    va_start( var_arg, n_values) ;

/*添加取自可变参数列表的值*/
    for (count = 0; count < n_values; count += 1) {
        sum += va_arg( var_arg, int);
    }

/*完成处理可变参数*/
    va_end(var_arg);
    return sum/n_values;
}

  可变参数的宏并不能判断参数数量和参数类型,而后者可能会造成缺省参数类型的提升。解决这两个问题的方法是使用命名参数,也就是可变参数列表中总有一个有名字的参数的原因。

第八章  数组

  int array[10];int *ap =array+2;在这之后,ap[0]在C里是合法的,它等同于array[2],ap[-1]同样是合法的,即array[1]。

  指针比数组更有效率的场合:for循环的ap++比循环体中的array[a] = 0有效率,前者的乘法计算只有一次,用于1与数据类型长度相乘,而后者每次都需要进行计算。


代码如下:

/* 使用数组 */
int array[10], a;
for ( a = 0 ; a< 10; a +=1 )
    array[a] = 0;

/* 使用指针 */
int array[10], *ap;
for ( ap = array ; ap< array + 10; ap ++ )
    *ap = 0;

  数组特别是庞大的数组的初始化时间可能非常可观,因此当数组的初始化局部于一个函数或代码块时,应当考虑程序每次都对其进行重新初始化是否值得。若否,把数组声明为static。

  使用指针访问多维数组的方法,例如对于数组int matrix[3][10],声明int *mp = matrix是错误的,因为matrix并非一个指向整型的指针,而是一个指向整型数组的指针。int (*p)[10] = matrix是可以的,p指向matrix第一行,实现对数组的逐行访问。如果需要逐个访问,则使用int *pi = &matrix[0][0]或int *pi = matrix[0],使它指向第一个元素。而 int (*p)[] = matrix;是不正确的,它的值根据空数组的长度调整,这一错误有的编译器不能捕捉到。函数传参数类似。

  多维数组显式初始化,只有第一维能够推算出,其他维不能省略。

第九章  字符串、字符和字节

  无符号数的谨慎使用:strlen返回无符号数,因此if(strlen(x) - strlen(y)>=0) ...永远是真。这种情况下应该写为if(strlen(x)>=strlen(y)) ...或者采用强制类型转换把其转为int。

  strtok保存它所处理的函数的局部状态信息,因此不能用它同时解析两个字符串。

  字符串函数遇到NULL字节结束操作,想要处理非字符串数据时不受到这个限制,可以使用另一组相关的函数:memcpy、memmove、memcmp、memchr、memset。

第十章  结构和联合

  参数为结构的函数,传递指针比传值调用更高效,这是因为后者需要建立一份结构的拷贝。f(type_struct *s){s->x};调用即为f(&s)。如果对这个结构的成员访问次数超过3次,声明为寄存器变量会更加有效。为了避免不适当的修改,可以把参数声明为const,将返回值赋给原结构(或它的一个成员)。

  位段只是进行了简单的了解,它是一种指定了成员长度的特殊结构。

第十三章  高级指针话题

  回调函数的使用可以解决类似于比较不明类型数据的问题,这里也是第一次系统地认识回调函数。

第十四章  预处理器

  消除多重包含的危险的方法,在每个头文件写入以下内容:


代码如下:

#ifndef _HEADRNAME_H
#define _HEADRNAME_H 1
/* All the stuff that you want in the header file*/
#endif

第一次被包含时将_HEADRNAME_H定义为1,再次被包含时将被忽略。即使把它写做#define _HEADRNAME_H都是可以的。但是仍应该尽可能避免多重包含。

第十五章  输入\输出函数

  freopen用于打开(或重新打开)一个特定的文件流,原型:FILE *freopen(char const *filename,char const *mode, FILE *stream),其中最后一个参数就是需要打开的流。它首先试图关闭这个流,然后用指定的文件和模式重新打开这个流,失败返回NULL,成功返回第三个参数。

  ungetc把先前读入的字符返回到流中,使它可以以后被重新读入。《C程序设计语言》上有一个字符处理的例子用到了它,在此复习一下。当fseek、fsetpos或rewind改变流的位置时,所有退回的字符都将被丢弃。

  gets和puts与fgets和fputs的区别在于,gets读取一行输入时,并不在缓冲区存储结尾的换行符,puts写入一个字符串时,它在字符串写入后向输出再添加一个换行符。另外gets不判断缓冲区长度,而这会造成危险。

  feof判断流是否处于文件尾,ferror报告流的错误状态,clearerr对指定流的错误标志进行重置。

  tmpfile创建一个临时文件保存数据,程序结束时就被删除。临时文件的名字由tmpnam创建。

第十六章  标准函数库

  volatile是类型修饰符,被设计用来修饰被不同线程访问和修改的变量,阻止编译器以一种可能修改程序含义的方式“优化”程序。

  vprintf、vfprintf、vsprintf用于打印可变参数列表,功能类似于对应的prinft等函数,但参数是一个可变参数列表arg。

  getenv获取环境变量,如果找到就用指针返回,否则返回NULL。

  locale是一组特定的参数,每个国家可能不同,目的是增强C的世界范围内的通用性,不详细记述。

  对于十七章经典抽象数据类型和第十八章运行时环境,前者已经比较熟悉,后者与汇编结合紧密,只是粗略浏览了一下,这本书姑且算是看完了。

(0)

相关推荐

  • Go 语言的指针的学习笔记

    Go 的原生数据类型可以分为基本类型和高级类型,基本类型主要包含 string, bool, int 及 float 系列,高级类型包含 struct,array/slice,map,chan, func . 相比 Java,Python,Javascript 等引用类型的语言,Golang 拥有类似C语言的指针这个相对古老的特性.但不同于 C 语言,Golang 的指针是单独的类型,而不是 C 语言中的 int 类型,而且也不能对指针做整数运算.从这一点看,Golang 的指针基本就是一种引用

  • C++学习笔记之类成员指针

    C++的类成员指针是一种奇葩的指针. 假设现在我们要表示一个三维的点,现在有两种定义方式: struct point1{ int x, y, z; }; struct point2{ int c[3]; }; 第一种的优点是更直观,但第二种可以方便的用for遍历三个属性.最终我选择了第一种,但是我还想弄一个遍历功能怎么办?这个时候类成员指针就排上用场了,我们可以创建一个“指向point中具体成员的指针”. int A:: *member = &A::x; 让member指针指向A中的成员x,且类

  • Go语言学习笔记之反射用法详解

    本文实例讲述了Go学习笔记之反射用法.分享给大家供大家参考,具体如下: 一.类型(Type) 反射(reflect)让我们能在运行期探知对象的类型信息和内存结构,这从一定程度上弥(mi)补了静态语言在动态行为上的不足.同时,反射还是实现元编程的重要手段. 和 C 数据结构一样,Go 对象头部并没有类型指针,通过其自身是无法在运行期获知任何类型相关信息的.反射操作所需要的全部信息都源自接口变量.接口变量除存储自身类型外,还会保存实际对象的类型数据. func TypeOf(i interface{

  • DB2 UDB V8.1管理学习笔记(三)

    正在看的db2教程是:DB2 UDB V8.1管理学习笔记(三).强制断开已有连接,停止实例并删除.  $ db2idrop -f instance_name 用于在UNIX下迁移实例. $ db2imigr instance_name 更新实例,用于实例获得一些新的产品选项或修订包的访问权. $ db2iupdt instance_name 获取当前所处的实例. $ db2 get instance 当更新实例级别或数据库级别的参数后,有些可以立即生效,有些需要重新启动实例才可生效.immed

  • JavaScript高级程序设计(第三版)学习笔记1~5章

    第2章,在html中使用JavaScript Html引入外部js脚本 <script type="text/javascript" src="test.js">两个</script>之间不应放脚本,因为并不会被执行</script> <script>标签有一个defer属性可以延迟脚本执行,但是并不保证会按脚本排列顺序执行 建议:将脚本引入放在<body>标签的所有内容之后,而不放在<head>

  • JavaScript继承学习笔记【新手必看】

    JavaScript作为一个面向对象语言(JS是基于对象的),可以实现继承是必不可少的,但是由于本身并没有类的概念,所以不会像真正的面向对象编程语言通过类实现继承,但可以通过其他方法实现继承.实现继承的方法很多,下面就只是其中的几种. 一. 原型链继承 function Person() { //被继承的函数叫做超类型(父类,基类) this.name='mumu'; this.age='18'; } Person.prototype.name='susu';//当属性名相同时需就近原则,先在实

  • jQuery学习笔记之回调函数

    1.回调函数定义 回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数.回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,才会真正的执行回调函数内部的方法. 2.代码 JS代码 (function($){ $.fn.shadow = function(opts){ //定义的默认的参数 var defaults = { copies: 5, opacity:0.1

  • javascript学习笔记(四)function函数部分

    函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块. Jscript 支持两种函数:一类是语言内部的函数(如eval() ),另一类是自己创建的. 在 JavaScript 函数内部声明的变量(使用 var)是局部变量,所以只能在函数内部访问它.(该变量的作用域是局部的). 您可以在不同的函数中使用名称相同的局部变量,因为只有声明过该变量的函数才能识别出该变量. 函数的调用方式 1.普通调用:functionName(实际参数...) 2.通过指向函数的变量去调用: var  myVar

  • JavaScript高级程序设计(第三版)学习笔记6、7章

    第6章,面向对象的程序设计 对象: 1.数据属性 configurable,表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性,默认为true enumerbale,表示能否通过for-in访问属性,默认true ƒwritable,表示能否修改属性值,默认true „value,数据存储位置,默认undefined 修改默认属性特性:Object.defineProperty(),接收三个参数:属性所在对象,属性名,描述符对象,描述符对象属性必

  • JavaScript学习笔记之DOM基础 2.4

    DOM的发展,与WEB标准化的大趋势相关甚密.只有基于正确的语义逻辑,DOM才能正确地发挥其功用.如今,正确的语义结构.表现与内容分离等要求,都已经成为网页设计中的基本要求.因此,在网页前端开发中,DOM的存在,无疑是为表现层.行为层甚至内容层面的连接提供了一个绝佳的API,成为热门的Ajax应用中不可或缺的组成部分. 一.平稳退化 1.概念 早期,在未使用JavaScript之前,网页中的内容可以正常显示出来,用户可以通过外设(如鼠标)操控浏览到相关内容,这种浏览体验对用户而言可能并不理想.

随机推荐