Hello, World!

helloworld.c
#include <stdio.h>

int main()
{
    printf("hello, world\n");
}

这个程序实现的效果是在终端命令行输出一行"hello, world",编译及执行这个程序:

root@DESKTOP-38B6GK1:~/C# gcc helloworld.c -o helloworld
root@DESKTOP-38B6GK1:~/C# ./helloworld
hello, world

sum = a + b

sum.c
#include <stdio.h>

int main()
{
    int a = 1;
    int b = 2;
    int sum = a + b;
    printf("sum is %d\n", sum);
} 

这个程序先定义了两个变量a和b,值分别为1和2,再定义变量sum并将a加b的和赋值给sum,最后输出sum的值,编译及执行这个程序:

root@DESKTOP-38B6GK1:~/C# gcc sum.c -o sum
root@DESKTOP-38B6GK1:~/C# ./sum
sum is 3

内存图

下面是在32位Linux系统下,一个进程的虚拟内存分布图,当前只需要注意以下几点:

  1. 32位系统下,进程的虚拟内存大小为4GB,也就是2^32Byte,范围为0x00000000~0xFFFFFFFF。
  2. 将这4GB虚拟内存进行分类,其中低3GB内存归进程使用,称为用户空间,范围是0x0000000~0xBFFFFFFF,最高的1GB归内核使用,称为内核空间,范围是0xC0000000~0xFFFFFFFF。
  3. 进程的所有内容都必须存放在这4GB的内存里,比如程序的二进制代码,以及运行过程中需要使用的数据。
  4. 靠近0x00000000地址的一小部分空间进程不能使用,比如典型的NULL地址,如果往这个地址存放数据,则程序会崩溃。
  5. 靠近3GB用户空间顶端的一小段内存称为栈空间,这里用于存放程序的局部变量,比如上面sum.c中定义的变量a, b, sum。

取地址与解地址

addr_ref.c
#include <stdio.h>

int main()
{
    int a = 1;
    int b = 2;
    int sum = a + b;
    
    printf("a is %d\n", a);
    printf("b is %d\n", b);
    printf("sum is %d\n", sum);

    // 取地址
    printf("address of a is %p\n", &a);
    printf("address of b is %p\n", &b);
    printf("address of sum is %p\n", &sum);

    // 解地址
    printf("a is %d\n", *(&a));
    printf("b is %d\n", *(&b));
    printf("sum is %d\n", *(&sum));
    
    // 以此类推...
    printf("address of a is %p\n", &(*(&a)));
}

编译运行效果:todo

这个程序先输出a, b, sum的值,再通过取地址符&获取到它们的内存地址,可以看到它们的地址的确位于靠近3G内存顶部的栈空间。拿到地址后,还可以通过解地址符*再解出这个地址对应的值。取地址和解地址的操作可以相互抵消。

五则运算

caculate.c
#include <stdio.h>

int main()
{
    int i = 20;
    int j = 3;
    printf("%d\n", i + j);
    printf("%d\n", i - j);
    printf("%d\n", i * j);
    printf("%d\n", i / j); // 整除
    printf("%d\n", i % j); // 取模
}

上面是整数的五则运行,注意整数的除法执行整除操作,结果仍是整数,而%则是取模操作,用于求余数,编译运行效果如下:

root@DESKTOP-38B6GK1:~/C# gcc caculate.c -o caculate
root@DESKTOP-38B6GK1:~/C# ./caculate
23
17
60
6
2

注释

注释是程序中的说明语句,用于解释某些语句的作用,其目的是为了方便阅读程序。注释不参与C语言的编译,任何注释都会在编译过程中都会被忽略。

注释有两种形式,分别是单行注释和多行注释,单行注释的形式是//注释内容,多行注释的形式是/*注释内容*/,如下:

#include <stdio.h>

int main() {
    // 这是单行注释 

    /* 这是多行注释
    可以跨行 */
}


注释形式

单行注释: //注释内容
多行注释: /注释内容/

注释规范

  1. 每个C文件开头都一段注释,标明文件的名称,作者,编写日期,文件描述与版本修改,下面是一个可以参考的例子:
/*=====================================================
*   Copyright (C) 2014 All rights reserved.
*   
*   Filename:calculate.c
*   Author:luqiang
*   Date:2015/01/12
*   Description:this is the description.
*
======================================================*/
  1. 每个函数的定义处都需要写注释,表示该函数的名称,作者,作用,以及该函数的输入输出值。
  2. 程序的关键代码需要用注释说明
  3. 注意注释不要过多,很多时候,可以通过合理的命名让程序具有*自解释性*。

关键字

关健字又称保留字,是预留给C语言本身使用的名词,在C语言中不可以用作其他用途,比如,不可以用关键字给变量命名,C89规定的关键字共有32个。根据关键字的作用分为四类:

  1. 数据类型关键字(12个)
  2. int, char, short, long, float, double, signed, struct, unsigned, union, enum,void
  3. 控制语句关键字(12个)
  4. break, case, continue, default, do, else, for, goto, if, return, switch, while
  5. 存储类型关键字(4个)
  6. auto, extern, register, static
  7. 其他关键字(4个)
  8. const, sizeof, typedef, volatile

数据类型

基本数据类型:int char float double
结合数据类型:数组[],指针*
构造数据类型:结构体,联合体,枚举类型
空类型:void

变量

变量代表一段有名字的内存空间,定义变量的方式是 数据类型 + 名称,比如int i 定义了一个整型变量。

标识符

用来表示程序中使用的变量名、数组名、函数名和其它由用户自定义的数据类型的名称。定义标识符的时候要注意以下几点:

  1. 只能由英文字母、数字和下划线构成,长度为1~32;
  2. 必须以字母或下划线“_”开头;
  3. 标识符严格区分大小写字母;
  4. 不能以C语言的关键字作为标识符;
  5. 标识符选用应尽量做到“见名知意”,即选用有含义的英文单词或缩写,如sum, name, max等。

运算符、操作数、表达式

运算符是可以对数据进行运算的符号,比如sizeof以及+ - * / %等符号。
运算符操作的对象称为操作数,运算符与操作数组合成表达式。表达式结尾没有分号,如果在表达式两边加上一个括号,那么表达式最终可以得到一个值,这是表达式与语句的区别。

程序错误分类

编译时错误

Error

程序中有语法错误,编译不通过,不能生成可执行文件。

Warning

程序中存在潜在的错误(比如printf未提供与占位符个数相等的参数),编译可以通过,可以生成执行文件。有些警告需要加-Wall选项才会提示Warning。

运行时错误

符合C语言语法规则,但是程序的逻辑设计与目的不符,比如程序要完成求乘法功能,写程序的时候写成了求加法。

  • 无标签