• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

cynchanpin

  • 博客园
  • 联系
  • 订阅
  • 管理

View Post

《C和指针》整理一

1.C语言的凝视
    在C语言中,假设须要凝视掉一段代码。且代码中可能会已经存在/**/凝视形式,那么能够使用:
#if 0
    statements
#endif
    这样的形式来凝视掉这段代码(statements代表这段代码)。

这样做的原因是C语言不同意嵌套凝视,也就是说第一个/*和第一个*/符号之间的内容都被看作凝视。无论里面还有多少个/*符号。

2.换行符的处理
#include <stdio.h>
int main()
{
    int ch, i;
    ch = getchar();
    i = getchar();
    printf("%d %d\n", ch, i);
    return 0;
}
输入:A+回车键
输出是多少?
分析:当我们使用getchar()函数接收一个字符时。在键盘输入至少须要键入一个字符(除非仅仅按一个回车键)和一个回车键。这样实际上用户是敲击了两次键盘。也就是实际上是键入了两个字符。也就是一个真正的字符和一个换行符(ASCII码为10相应的字符为'\n')。

所以上面这样的情况下的输出会输出A的ASCII码和换行符的ASCII码。正是因为这样的情况的存在,假设我们须要接收用户从键盘输入的字符时。须要程序中自己主动清理掉其后面跟的换行符。防止这个换行符污染到下一次的输入中。相同的以下的一个样例:
#include <stdio.h>
int main()
{
    int ch, ca, cb;
    scanf("%d", &ch);
    scanf("%d", &ca);
    cb = getchar();
    printf("%d %d %d\n", ch, ca, cb);
    return 0;
}
输入为:12+回车+34+回车的时候输出为多少?
分析:输出为12 34 10。原因和上面几乎相同。可是scanf比較智能,在接收输入的时候会自己主动去匹配stdin中的下一个输入时候匹配类型,匹配则接收。不匹配则丢弃,所以34还是可以正确的接收,然而最后的一个换行符(键盘上敲击回车键产生的)还是会留在stdin中,所以最后会被getchar()函数接收到cb中。

3.读取字符时的注意

    一个常常闻到的问题是:为什么ch被声明为整数。而我们实际上须要用它来读取字符(这个在从文件里读取字符时尤其须要注意)?答案是EOF是一个整型值,它的位数比字符类型要多,把ch声明为整型能够防止从输入读取的字符意外地被解释为EOF。

但同一时候,这也意味着接收字符的ch必须足够大。足以容纳EOF。这就是ch使用整型值的原因。字符仅仅是小整数而已。所以用一个整型变量容纳字符值并不会引起不论什么问题。

4.三字母词和转义字符
    C的标准中定义了几个三字母词,三字母词也就是几个字符的序列,合起来表示还有一个字符。

三字母词使C环境能够在某些缺少一些必需字符的字符集上实现。这里列出了一些三字母词以及它们所代表的字符:
??

(  [        ?

?<  {       ??

=  #
??)  ]        ??

>  }       ??/  \
?

?!  |        ??'  ^       ??

-  ~
比方printf("??

)");将输出一个]符号。
转义字符:
\?

  在书写连续多个问号时使用。防止它们被解释为三字母词。
\"  用于表示一个字符串常量内部的双引號。


\'  用于表示字符常量'。
\\  用于表示一个反斜杠,防止它被解释为一个转义字符。

5.关于数据类型大小的ANSI规定
整型之间的大小规则:长整型至少应该和整型一样长,而整型至少应该和短整型一样长。
浮点型之间的大小规则:long double至少和double一样长,而double至少和float一样长。

6.变量作用域
编译器能够确认4种不同的作用域——文件作用域、函数作用域、代码块作用域和原型作用域。标识符声明的位置决定它的作用域。


>>> 代码块作用域
    位于一对花括号之间的全部语句被称为一个代码块。

下图中的5、6、7、9、10的变量都具有代码块作用域,函数的形式參数5在函数体内部也具有代码块作用域。另外声明9的f和声明6的f是不同的变量,在声明9所在的花括号内6声明的f被隐藏。
>>> 文件作用域
    不论什么在全部代码块之外声明的标识符都具有文件作用域,它表示这些标识符从它们的声明之处直到它所在的源文件结尾处都是能够訪问的。

下图中1、2具有文件作用域,同一时候文件里定义的函数名也具有文件作用域,由于函数名本身不属于不论什么代码块。所以4也具有文件作用域。
>>> 原型作用域
    原型作用域仅仅适用于在函数原型声明中的參数名,下图中3、8具有这种作用域。在原型中,參数的名字并没必要。
>>> 函数作用域
    它仅仅适用于语句标签,语句标签用于goto语句。基本上,函数作用域能够简化为一条规则——一个函数中的全部语句标签必须唯一。




7.链接属性
    链接属性一共同拥有3种——external(外部)、internal(内部)和none(无)。没有链接属性的标识符(none)总是被当做单独的个体。也就是说该标识符的多个声明被当作独立不同的实体。属于internal链接属性的标识符在同一个原文件内的全部声明中都指同一个实体。但位于不同源文件的多个声明则分属不同的实体。最后。属于external链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体。


    函数名、外部变量默认的链接属性为external。而局部变量的默认链接属性为none。

同一时候keywordextern和static用于在声明中改动标识符的链接属性,假设某个声明在正常情况下具有external链接属性,在它的前面加上statickeyword能够使它的链接属性变为internal。static仅仅对缺省链接属性为external的声明才有改变链接属性的效果。而externkeyword的规则更为复杂,一般而言,它为一个标识符指定external链接属性。这样能够訪问在其它不论什么位置定义的这个实体。

例如以下的样例:
static int i;
int func()
{
    int j;
    extern int k;
    extern int i;
    ......
}
分析:extern int k;这个声明表示在该函数体内的兴许部分能够訪问其它源文件里定义的k变量实体。而当externkeyword用于源文件里一个标识符的第一次声明时,它指定该标识符具有external链接属性;可是假设它用于该标识符的第2次或以后的声明时。它并不会更改由第1次声明所指定的链接属性。所以extern int i;声明不会改动i的static属性。



8.数组名
    在C中,差点儿全部使用数组的表达式中,数组名的值都是一个指针常量。也就是数组第一个元素的地址。仅仅有在两种场合下数组名并非指针常量:作为sizeof操作符的运算对象。当做单目运算符&的操作数。

9.指针与下标
    下标绝不会比指针更有效率,但指针有时会比下标更有效率。
样例1:
int array[10], a;
for(a = 0; a < 10; a++)
    array[a] = 0;
    为了对下标表达式求值(array[a])。编译器在程序中插入指令,取得a的值,并把它与整型的长度相乘。这个乘法须要花费一定的时间和空间。再看指针形式的写法:
int array[10], *ap;
for(ap = array; ap < array+10; ap++)
    *ap = 0;
    虽然这里不存在下标,可是还是存在乘法运算。

1这个值必须与整型的长度相乘,然后再与指针相加。可是这里存在一个重大的差别:循环每次运行时,运行乘法运算的都是两个同样的数(1和4)。结果这个乘法仅仅有在编译时运行一次——程序如今包括了一条指令。把4与指针相加。程序在运行时并不运行乘法运算。可是以下的两段代码:
a = get_value();
array[a] = 0;

a = get_value();
*(array+a) = 0;
    两组语句产生的代码并无差别。a可能是不论什么值,在执行时才干知道。所以两种方案都须要乘法指令,用于对a进行调整。

这个样例说明了指针和下标的效率全然同样的场合。

10.指针的进一步优化
void try1()
{
    for(p1 = x, p2 = y; p1-x < SIZE;)
        *p1++ = *p2++;
}
●这段代码使用p1-x < SIZE来作为终点推断。可是这个推断事实上会被处理为除法。由于p1-x的值是指针的运算,这个差值必需要除以一个类型的大小做调整。

这一点事实上会耗费比較大的代价。


void try2()
{
    for(i = 0, p1 = x, p2 = y; i < SIZE; i++)
        *p1++ = *p2++;
}
●这段代码又一次使用了计数器,用于控制循环退出。这样能够消除除法。而且缩短汇编后的代码长度。可是和上面的代码一样。代码在运行时p1和p2一般都会被又一次拷贝到别的寄存器之后才開始运算。


void try3()
{
    register int *p1, *p2;
    register int i;
    for(i= 0, p1 = x, p2 = y; i < SIZE; i++)
        *p1++ = *p2++;
}
●这段代码的优化在于将p1和p2声明为寄存器变量。这种话在汇编之后能够降低复制p1和p2的值到别的寄存器的步骤。
void try4()
{
    register int *p1, *p2;
    for(p1 = x, p2 = y; p1 < &x[SIZE]; )
        *p1++ = *p2++;
}
●&x[SIZE]会在编译时求值(由于SIZE是个数字常量),这段代码进一步消除掉了计数器i,汇编之后的代码相当紧凑,差点儿达到和汇编代码一样的效率。这同一时候也是C语言不正确称边界以及不检查数组越界给编程带来的方便体现。

posted on 2017-04-16 16:13  cynchanpin  阅读(255)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3