本文的目的是记录平时工作学习过程中学习到的C语言知识,由于单独一篇文章记录的话可能篇幅过少,因此置顶此文用作此用处,本文从2017-12-16开始记录,后续新增内容不单独列出时间,在MarkEditor中有相应的版本记录。

文件描述符

EOF(end of file)文件结束,参考如下代码:

#include <stdio.h>

int main()
{
    int c;
    while((c = getchar())!=EOF)
    {
         printf("%c",c);
    }
    printf("\n");
    printf("Last time %d\n",c);
    return 0;
}

当输入的字符不是EOF时会打印输入的字符,然后输入EOF时退出while循环,我们通过printf打印出该值,之所以不把c声明为char,是因为它必须足够大,除了能存储任何可能的字符外还要能存储文件结束符EOF。

下面的程序是统计输入的行数 单词数和字符数,程序虽小,短小精悍:

#include <stdio.h>
#define IN 1
#define OUT 0

int main()
{
    int c,nl,nw,nc,state;
    state = OUT;
    nl = nw = nc = 0;
    while((c = getchar()) != EOF)
    {
        ++nc;
        if(c=='\n')
            ++nl;
        if(c==' '|| c=='\t'|| c=='\n')
            state = OUT;
        else if(state == OUT)
        {
            state = IN;
            ++nw;
        }

    }
    printf("%d %d %d\n",nl,nw,nc);
    return 0;
}

该程序对单词的定义比较宽,它是任何不包含空格、制表符或换行符的字符序列。

函数参数为空

为了与老版本的C语言程序兼容,ANSI C语言把空的参数表看成老版C语言的声明方式,并且对参数表不再进行任何检查。在ANSI C中如果要声明空参数表,则必须使用关键字void进行显示声明。
“定义”:表示创建变量或分配存储单元
“声明”:说明变量的性质,但并不分配存储单元

类型 运算符 表达式

'x' 与“x”的区别:
'x'是一个整数,其值是字母x在机器字符集中对应的数值。
“x”是一个包含一个字符以及一个结束符'\0'的字符数组。
define语句与枚举的区别:
define编译器不检查这种类型变量中存储的值是否为该枚举的有效值。枚举变量提供这种检查,因此枚举比define更具优势。
默认情况下外部变量的值与静态变量的值将被初始化为0.未经显示初始化的自动变量的值为未定义。

按位运算符

我们必须将按位运算符& | 同逻辑运算符&& ||区分开来,后者用于从左至右求表达式的真值。例如,如果x的值为1,y的值为2,那么,x&y的结果为0,而x&&y的值为1.
x=x&~077
设置x的最后6位为0,表达式x=x&~077与机器字长无关,它比形式为x&0177700的表达式要好,因为后者假定x是16位的数值,这种可移植的形式并没有增加额外开销。
~(~0 << n)
~0 << n将~0左移n位,并将最右边的n位用0填补。再使用~运算对它按位取反,这样就建立了最右边n位全1的屏蔽码。
二分查找的另一种形式:在while中只比较了两次,通过打印low mid high的值可以清楚的了解查找过程。

int binsearch(int x, int v[], int n)
{
    int low, high, mid;
    int times = 0;
    low = 0;
    high = n - 1;
    mid = (low + high) / 2;
    while (low <= high &&v[mid]!=x)
    {
        times += 1;
        printf("%d times,low=%d,mid=%d,high=%d.\n",times,low,mid,high);
        if (x < v[mid])
            high = mid - 1;
        else
            low = mid + 1;
        mid = (low + high) / 2;
        
        
    }
    printf("Last time %d times,low=%d,mid=%d,high=%d.\n",times+1,low,mid,high);

    /* 虽然去掉了相等的测试,但如果有相等的情况,必然在v[mid]中 */
    if (v[mid] == x)
        return mid;
    else
        return -1;
}

运算符优先级


在switch语句中case的作用只是一个标号,因此,某个分之中的代码执行完后,程序将进入下一分支继续执行,除非在程序中显示地跳转。跳出switch语句最常用的方法是使用break与return 语句。break语句还可强制控制流从while for 和do while循环语句中立即退出。

数组与指针的区别

#include <stdio.h>
#include <string.h>
int GetSize(int data[])
{
    return sizeof(data);
}

int main()
{
    int data1[]={1,2,3,4,5};
    int size1 = sizeof(data1);
    int *data2 = data1;
    int size2 = sizeof(data2);
    
    int size3 = GetSize(data1);
    printf("%d %d %d\n",size1,size2,size3);
    return 0;
}

在64位系统运行结果为20 8 8,size1=4*5,size2是指针,64位的话指针占用8个字节,当数组作为函数参数进行传递时,数组自动退化为同类型的指针。所以size2和size3相同。
如果在xcode中编辑上述代码,会在代码位置出现如下提示:
Sizeof on array function parameter will return size of 'int *' instead of 'int []'

二维数组作为函数参数

daytab[2][13]={
{0,31,28,31,30,31,30,31,31,30,31,30,31},
{0,31,29,31,30,31,30,31,31,30,31,30,31}
}
如果将二维数组作为参数传递给函数,那么函数的参数声明中必须指明数组的列数。数组的行数没有太大关系,函数调用时传递的是一个指针,它指向由行向量构成的一维数组,其中每个行向量是具有13个整型元素的一维数组,
在该例子中,传递给函数的是一个指向很多对象的指针,其中每个对象是由13个整型元素构成的一维数组。因此,如果将数组daytab作为参数传递给函数f,那么f的声明应该写成如下形式:
f(int daytab[2][13]){……}
也可以写成
f(int daytab[][13]){……}
由于数组的行数无关紧要,所以该声明还可以写成:

f(int (*daytab)[13]){……}

这种声明形式表明参数是一个指针,它指向具有13个整型元素的一维数组。因为方括号[]的优先级高于*的优先级,所以上述声明中必须使用圆括号,如果去掉圆括号

int *daytab[13]

则变成声明一个数组,该数组有13个元素,其中每个元素都是一个指向整型对象的指针。一般来说,除数组的第一维可以不指定大小外,其余各维必须明确指定大小。

goto语句与标号

goto语句最常见的用法是终止程序在某些深度嵌套的结构中的处理过程,例如一次跳出两层或多层循环。这种情况下使用break语句是不能达到目的的,它只能从最内层循环退到上一级的循环。

输出格式化控制

flag width.precision

The flag can be any of:

flag meaning

- left justify

+ always display sign

space display space if there is no sign

0 pad with leading zeros

# use alternate form of specifier
%#o adds a leading 0 to the octal value

%#x adds a leading 0x to the hex value

%#f or

%#e ensures decimal point is printed

%#g displays trailing zeros

作用域规则

名字的作用域指的是程序中可以使用该名字的部分。对于在函数开头声明的自动变量来说,其作用域是声明该变量名的函数。不同函数中声明的具有相同名字的各个局部变量之间没有任何关系。函数的参数也是这样的,实际上可以将它看作是局部变量。
如果函数没有参数,则使用void进行声明。
变量声明用于说明变量的属性,主要是变量的类型,而变量的定义除此以外还将引起存储器的分配。
外部变量的定义中必须指定数组的长度,但extern声明则不一定要指定数组的长度。外部变量的初始化只能出现在其定义中。
寄存器变量:register声明告诉编译器,它所声明的变量在程序中使用频率较高,其思想是,将register变量放在机器的寄存器中,这样可以使程序更小、执行速度更快。编译器可以忽略此选项。register声明只适用于自动变量以及函数的形式参数,编译器可以忽略过量的不支持的寄存器变量声明,无论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的。

变长数组

C语言标准C99中支持变长数组(VLA)

#include <stdio.h>

int main()
{
    int i,n;
    scanf("%d",&n);
    int a[n];
    for(i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
    }

    for(i=0;i<n;i++)
    {
        printf("%d ",a[i]);
    }
    printf("\n");

    return 0;
}

C99支持变长数组,用户输入n,然后定义数组长度,是可以有变长数组的,即可以存在Arrays of Variable Length,也就是说,上面的代码在支持C99的编译器中是合法的。

但在GNU C下有点小特殊,使用gcc -std=c89编译上述代码,发现也是OK的!原来,变长数组在c89中是作为GNU C的一个扩展存在的。具体可以参考gcc的手册.
确定当前gcc默认使用的标准可以使用如下代码:

#include <stdio.h>

int main(void)
{
#ifdef __STDC__
     printf("%s\n", "stardard C");
#endif
#ifdef __STDC_VERSION__
     printf("%ld\n", __STDC_VERSION__);
#endif
     return 0;
}

布尔类型

长期缺乏布尔类型的问题在C99中得到了解决。C99提供了_Bool型,除了_Bool类型的定义,C99还提供了一个新的头,这使得操作布尔值更加容易。该头提供了bool宏,用来代表_Bool,示例代码如下:

#include <stdio.h>
#include <stdbool.h>
int main()
{
    bool flag=true;
    if(flag)
    {
        printf("Hello,world.\n");
    }

    return 0;
}

如果你的编译器不支持该头文件,需要自己定义实现,一种可行的方案是:

#define true 1
#define false 0
typedef int bool;

数组初始化

如果数组中只有相对较少的元素需要进行显示的初始化,而其它元素可以进行默认赋值。考虑如下例子:
int a[2000];
假如我们只需要对a[20],a[40],a[1500],a[1999]四个元素赋值,C99中的指定初始化式可以用于解决这一问题。
int a[2000]={[20]=1,[40]=2,[1500]=3,[1999]=4};

交换变量

不使用第三个变量交换两个变量的值,除了异或,比较好理解的一种方式如下:

#include <iostream>
using namespace std;
int main()
{
    int a=20,b=10,*p1=&a,*p2=&b;
    cout<<"Before swap: ∗p1="<<*p1<<" ∗p2="<<*p2<<endl;
    *p1=*p1+*p2;
    *p2=*p1-*p2;
    *p1=*p1-*p2;
    cout<<"After swap: ∗p1="<<*p1<<" ∗p2="<<*p2<<endl;
    return 0;
}

程序输出:

Before swap: ∗p1=20 ∗p2=10
After swap: ∗p1=10 ∗p2=20