C语言学习笔记

##########################
一, 类型,运算符与表达式
##########################

所有整型都包括sign(带符号)和unsign(无符号)两种形式.
C语言只提供了下面几种基本数据类型:
    char, int, float, double

一个字符常量是一个整数,书写时将一个字符括在单引号中,如: 'x'.

字符串常量也叫字符串字面值, 是用双引号括起来的0个或多个字符组成的字符序列.

我们已经字符常量与仅包含一个字符的字符串之间的区别: 'x'与"x"是不同的.前者是一个整数,其值是字母x在机器字符集中对应的数值; 
后者是一个包含一个字符以及结束符'\0'的字符数组.

枚举常量是另外一种类型的常量,是一个常量整型值的列表:
    enum boolean {
        NO, YES
    };

所有变量都必须先声明后使用.

任何变量的声明都可以使用const限定符限定,表明变量的值不能被修改.

取模运算符 % 不能应用于float或double类型.

C语言中,很多情况下会进行隐式的算术类型转.一般来说如果二元运算符的两个操作数具有不同的类型,那么在进行运算之前先把较低的类型提升为较高的类型.

强制类型转换: (类型名) 表达式
-------------

##########################
二, 函数与程序结构

##########################


外部变量或函数的作用域从声明它的地方开始, 到其所在的文件的末尾结束. 例如:
    main() {...}

    int sp = 0;


    void push(double f) {...}
那么在push()函数中,不需声明就可以访问变量sp, 但是sp却不能在main()中使用,
push()也不能在main()中使用.
如果要在外部变量的定义之前使用该变量, 必须在相应的变量声明中使用关键字: extern.

用static声明限定的外部变量与函数,可以将其的作用域限定为被编译源文件的剩余部分.通过static限定外部对象,可以达到隐藏外部对象的目的.
要将对象指定为静态存储,可以在正常的对象声明之前加上关键字static作为前缀.
通常情况下,函数名字是全局可以访问的,
但是如果吧函数声明为static,则该函数除了对该函数声明所在的文件可见外,其他文件都无法访问.

外部变量和静态变量都属于静态区存储,不同的是静态变量限定了作用域(同源文件).

register声明的变量放在机器的寄存器中,这样可以更快.
register声明只适用于自动变量以及函数的形式参数.
无论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的.

C语言中,不允许在函数中定义函数.


数组的初始化可以在声明的后面紧跟一个初始化表达式列表:
    int days[] = {31, 28, 31, 30, ... }
字符数组的初始化比较特殊:
    char pattern[] = "some"; //它同下面的声明是等价的:
    char pattern[] = {'s','o','m','e','\0'}

宏替换中的替换文本是任意的:
    #define forever for ( ; ; )
宏定义也可以带参数:
    #define max(A, B) ((A) > (B) ? (A) : (B))
    使用宏max看起来就像是使用函数:
    x = max(p + q, r + s);
可以通过#undef指令取消名字的宏定义:
    #undef getchar

形式参数不能用带引号的字符串替换,但是,如果在替换文本中,参数名以#作为前缀,
则结果将被扩展为由实际参数替换该参数的带引号的字符串:(这翻译真垃圾)
    #define dprint(expr) printf(#expr " = %g\n", expr)
    使用语句: dprint(x/y);
    调用宏: printf("x/y" " = %g\n", x/y);
    等价于: printf("x/y = %g\n", x/y);

预处理器运算符 ## 为宏扩展提供了一种连接实际参数的手段: 
    #define paste(front, back) front ## back
    宏调用paste(name, 1)的结果将建立记号name1.

预处理的条件控制可以使用 #if ... #elif ... #else ... #endif:
    #if !defined(HDR)
    #define HDR
    ...
    #endif
C语言专门定义了两个预处理语句: #ifdef 与 #ifndef, 用来测试某个名字是否已经定义.
-----------------

##########################
三, 指针与数组
##########################

指针是一种保存变量地址的变量.

地址运算符&只能应用于内存中的对象,即变量与数组元素.
不能作用于表达式,常量或register变量.

一元运算符*是间接寻址或间接引用运算符.当它作用于指针时,将访问指针所指向的对象.

double *dp, atof(char *);这个表达式表明*dp
和atof(s)的值都是double类型,且atof的参数是一个指向char类型的指针.

*ip += 1; 等同于 ++*ip 或 (*ip)++

指针与数组:
    int a[10];
    int *pa;
    pa = &a[0];
"指针加1"就意味着, pa+1 指向下一个对象,
下标和指针运算之间具有密切的对应关系.根据定义,数组类型的变量或表达式的值是该数组第0个元素的地址.
那么pa = &a[0]也可以写成下面形式:
    pa = a;
对数组元素a[i]的引用也可以改成*(a+i)形式.
&a[i]和a+i的含义是相同的,a+i是a之后第i个元素的地址.
但是我们必须记住,数祖名和指针之间有一个不同之处: 指针是变量,数祖名不是变量.pa
=a和pa++是合法的, 但a = pa 和 a++ 是非法的.

进栈和出栈的标准用法:
    *p++ = val; // 将val压入栈
    val = *--p; // 将栈顶元素弹出到val中

/* strcpy函数: 将指针t指向的字符串复制到指针指向的位置 */
版本1(数组下标实现):
    void strcpy(char *s, char *t) {

        int i;

        i = 0;
        while (s[i] = t[i] != '\0')
            i++;
    }
版本2(指针方式实现):
    void strcpy(char *s, char *t) {
        while ((*s = *t) != '\0') {
            s++;
            t++;
        }
    }
版本3(指针):
    void strcpy(char *s, char *t) {
        while ((*s++ = *t++) != '\0')
            ;
    }
版本4(指针):
    void strcpy(char *s, char *t) {
        while (*s++ = *t++)
            ;
    }

char *lineptr[MAXLINES];
这个表达式表示lineptr是一个具有MAXLINES个元素的一维数组,其中数组的每个元素是一个指向字符类型对象的指针.

看下面这两个定义:
    int a[10][20];
    int *b[10];
从语法角度讲,a[3][4]和b[3][4]都是对一个int对象的合法引用.但a是一个真正的二维数组,它分配了200个int类型的存储空间.
而对b来说,仅仅是分配了10个指针,并且没有对他们初始化.
指针数组的一个重要的优点在于,数组的每一行长度可以不同.

C语言中,主函数main()可以带两个参数:argc(参数数量), argv(指针,对应每个函数).
看下面的例子: 可以实现echo,比如echo hello world会打印hello world;
    main(int argc, char *argv) {
        int i;
        for (i = 1; i < argc; i++) {
            printf("%s%s", argv[i], (i < argc - 1) ? " " : "");
        }
        printf("\n");
        return 0;
    }
其中核心代码可以优化:
    while (--argc > 0) {
        printf((argc > 1) ? "%s " : "%s", *++argv);
    }

UNIX系统中的C语言有个约定:以负号开头的参数表示一个可选标志或参数,如 -x.
一般在程序里这样获取: ... (*++argv)[0] == '-' 

在C语言中,函数本身不是变量,但可以定义指向函数的指针.
这种类型的指针可以被赋值,存放在数组中,传递给函数以及作为函数的返回值.
指向函数的指针使函数"变量化"了.

int (*comp)(void *, void *);
它表明comp是一个指向函数的指针,该函数具有两个void*类型的参数.其返回值为int.

int *comp(void *, void *);
则表明comp是一个函数,该函数返回一个指向int类型的指针.

下面是一个复杂的声明:
    char (*(*x())[])();
表明x是一个函数, 它返回一个指针,
该指针指向一个一维数组,该数组的元素为指针,这些指针分别指向多个函数,这些函数的返回值为char类型.
------------

##########################
四, 结构
##########################

结构是一个或多个变量的结合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字之下.
struct point {
    int x;
    int y;
}
struct后面的名字是可选的,结构中定义的变量称为成员.
struct声明定义了一种数据类型:
    struct {...} x, y, z; 
    从语法角度来说,这种声明方式与 int x, y, z;具有类似的意义.
struct point pt; 定义了一个struct point类型的变量pt;
在表达式中,可以通过下列形式引用某个结构中的成员:
    结构名.成员

结构可以嵌套:
    struct rect {
        struct point pt1;
        struct point pt2;
    }

下面函数带有两个整型参数, 并返回一个point类型的结构:
    struct point makepoint(int x, int y) {
        struct point temp;
        temp.x = x;
        temp.y = y;
        return temp;
    }

如果传递给函数的结构很大,使用指针方式的效率比较高,结构指针类似于普通变量指针:
    struct point *pp;
如果pp指向一个point结构, 那么*pp即为该结构,(*pp).x则是结构成员:
    struct point origin, *pp;
    pp = &origin;
    printf("origin is (%d,%d)\n", (*pp).x, (*pp).yy);
    其中的圆括号是必须的, "."优先级比"*"高.
为了方便,C语言提供了另一种结构指针的写法. 假设p是一个指向结构的指针,可以用:
    p->结构成员

所有运算符中,下面4个运算符优先级最高:
结构运算符"."和"->",用于函数调用的"()"以及用于下标的"[]".

结构声明和对变量的类型定义可以写在一起:
    struct key {
        char *word;
        int count;
    } keytab[NKEYS];    

C语言提供了一个编译时一元运算符sizeof, 它可用来计算任一对象的长度,
但条件编译语句#if中不能使用sizeof.

一个包含其自身实例的结构是非法的, 但是, 下列声明是合法的:
    struct tnode {
        char *word;
        int count;
        struct tnode *left;
        struct tnode *right;
    }
它将left声明为指向tnode的指针,而不是tnode实例本身.

C语言提供了一个称为typedef的功能,它用来建立新的数据类型名:
    typedef int Length;
    将Length定义为与int相同意义的类型名字.它没有创建新的类型,只是一个增加了新名称而已.

struct {
    unsigned int is_keyword : 1;
    unsigned int is_extern : 1;
    unsigned int is_static : 1;
} flags;
这里定义了一个变量flags, 它包含3个一位的字段.
冒号后的数字表示字段的宽度(用二进制位数表示).



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值