《The C Programming Language, 2nd Edition》K&R Chapter 2 - Types, Operators and Expressions 笔记

名称

变量名由数字、字母、下划线组成,其中数字不能开头,下划线不建议开头,因库例程通常以下划线开头。
内部名(内部链接)至少前31个字符有效,外部名(外部链接)至少前6个字符有效。

变量类型

C语言只提供四种基本类型:

  1. char,占1 byte,能够容纳本地字符集的一个字符。
  2. int,整数,反映具体的机器整数的最自然长度。
  3. float,单精度浮点。
  4. double,双精度浮点。

short和long为限定符。

  1. short int和long int等价于short和long。
  2. short通常16位。int通常16或32位。long通常32位。
  3. short和int至少16位。long至少32位。
  4. short不大于int,int不大于long。

signed和unsigned,适用于char和int。

float、double、long double的长度可以相同。

取决于机器的类型的长度具体见<limits.h>和<float.h>。

char有无符号取决于机器。
char转换为int时,符号扩展还是0扩展也取决于机器。
C语言只保证可打印的字符为正值。

常量

整数,后辍为l或L为long,u或U为unsigned,ul或UL为unsigned long。
无后辍的整数,int能容纳为int,int不能容纳时,为能容纳它的最小整数类型(无法确定有无符号)。

浮点,无后辍为double,f或F为float,l或L为long double。

前辍0表示8进制,0x或0X为16进制,8和16进制数的后辍同理。
'\0xx'同前辍0,'\xhh'同辍0x。

'\0'为0。

常量表达式在编译时求值,比如char line[INT_MAX + 1];
字符串常量也称为字符串字面值,在编译时连接,比如"hello, " "world"等价于"hello, world"

strlen()不包含字符串末尾的'\0'。

字符常量的值取决于字符集,推荐使用<ctype.h>库函数进行操作。比如转换为小写字母tolower(c)。在ASCII字符集中大小写字母的间隔是固定的,但EBCDIC字符集中不成立。

枚举

enum boolean {NO = 1, YES};
enum boolean b = NO;

定义枚举时,成员按顺序自增1,第一个成员默认为0。

与#define相比,枚举优势为:

  1. 常量值自动生成。
  2. 为枚举变量赋值时,编译器会进行类型检查。(不会进行枚举值是否有效的检查)
  3. Debug程序会使用枚举名显示枚举值。

声明与定义

变量必须先声明后使用(含隐式声明)。
变量声明时可以初始化,声明同时初始化是一个定义。

静态变量,在程序开始时初始化,初始化表达式必须为常量, 默认初始值为0。
自动变量,每次进入函数时初始化,初始化表达式任意,默认初始值未定义。
外部变量和static变量都是静态变量。

const变量,不能通过这个变量直接修改它的值。
const数组,不能通过这个数组直接修改它元素的值。

运算符

%不能用于浮点。

整数除法和%处理负数的结果取决于机器。

优先级(从高到低):

  1. 算术运算符「一元+和-」>「* / %」>「二元+和-」
  2. 关系运算符「> >= < <=」>「== !=」
  3. 逻辑运算符「&&」>「||」(短路运算符)
  4. 赋值运算符「 =」

关系和逻辑运算符结果为真返回1,为假返回0。

逻辑非运算符「!」将非0变为0,0变为1。

自增运算符的操作数只能是变量,(i+j)++是非法的。

按位操作符「& | ^ << >> ~」只用于整数。
「& | 」不是短路运算符。
「^」异或,相同为0,不同为1。
「>>」符号位扩展还是0扩展取决于机器。

复合赋值运算符的优势时,左边的表达式只求值一次。

条件运算符的后两个表达式只有一个被求值。
条件运算符的优先级仅比赋值运算符高。

C语言没有约定多元运算符中操作数的求值顺序,取决于机器。比如x = f() + g();中f()和g()的求值顺序是未定义的。

函数调用,复合赋值,自增和自减运算符可能产生副作用(改变变量的值)。多元运算符中操作数的副作用生效的顺序,取决于机器,比如a[i] = i++;
C语言约定所有实参的副作用,在函数调用之前生效。

运算符优先级与结合性如下:

类型转换

二元运算符会将较窄的操作数,转换为较宽的操作数的类型,再进行运算。

  1. char和short运算时转换为int。
  2. float运算时不会转换为double。
  3. 整数与浮点运算时转换为浮点。
  4. 同类型运算时signed转换为unsigned。

假设int为16位,long为32位,那么-1L<1U,-1L>1UL。

整数较宽的类型转换为较窄的类型时高位丢弃。
浮点转换为整数时,小数丢弃。
double转换为float时,是四舍五入还是截取取决于机器。

没有函数原型的情况下,实参char、short转换为int,float转换为double。

强制转换运算符与一元运算符优先级相同。
强制转换不改变变量的值。
有函数原型的情况下,实参按形参类型,自动强制转换。

例程

strlen函数

/* strlen: return length of s */
int strlen(char s[])
{
    int i;

    i = 0;
    while (s[i] != '\0')
        ++i;
    return i;
}

lower函数(将字符转为小写,仅ASCII有效)

/* lower: convert c to lower case; ASCII only */
int lower(int c)
{
    if (c >= 'A' && c <= 'Z')
        return c + 'a' - 'A';
    else
        return c;
}

rand和srand函数

unsigned long int next = 1;

/* rand: return pseudo-random integer on 0..32767 */
int rand(void)
{
    next = next * 1103515245 + 12345;
    return (unsigned int)(next/65536) % 32768;
}

/* srand: set seed for rand() */
void srand(unsigned int seed)
{
    next = seed;
}

字符串中删除所有指定字符

/* squeeze: delete all c from s */
void squeeze(char s[], int c)
{
    int i, j;

    for (i = j = 0; s[i] != '\0'; i++)
        if (s[i] != c)
            s[j++] = s[i];
    s[j] = '\0';
}

strcat函数(字符串连接)

/* strcat: concatenate t to end of s; s must be big enough */
void strcat(char s[], char t[])
{
    int i, j;

    i = j = 0;
    while (s[i] != '\0') /* find end of s */
        i++;
    while ((s[i++] = t[j++]) != '\0') /* copy t */
        ;
}

返回整数x中从第p位(0位起向左)开始右n位(含p)

/* getbits: get n bits from position p */
unsigned getbits(unsigned x, int p, int n)
{
    return (x >> (p+1-n)) & ~(~0 << n);
}

统计整数x中值为1的二进制位数

/* bitcount: count 1 bits in x */
int bitcount(unsigned x)
{
    int b;

    for (b = 0; x != 0; x >>= 1)
        if (x & 01)
            b++;
    return b;
}