C 语言期末重难点

数据表现与运算

常量

八进制整数:以0开头,如 0123 就是一个八进制整数

十六进制整数:以 0x0X 开头,如 0x120xFF

科学计数法:以 e 或者 E 表示以 10 为底的指数,e 前需有数字,e 后必须为整数。如 1e101.2E-12

普通字符:由单引号包裹。字符常量存储的是其 ASCII 码,本质是一个数字,所以可以参与运算。例如 'A' + 1 的结果是 B

转义字符:通常是控制字符,用来控制终端的光标。常用的如:

  • \n:换行,将光标移动到下一行的开头
  • \t:水平制表符,将光标移动到下一个制表符的位置
  • \\:因为 \ 表示转义,所以需要输出反斜杠的时候需要再次转义。

字符串:由双引号包裹。

符号常量:由 #define 定义的常量。如 #define PI 3.14在编译时,程序中所有的 PI 符号都会被直接替换为 3.14。

变量

先定义,后使用。C89 标准中,一个代码块(大括号包裹的块)中所有的变量需要在代码块的开头定义好。

标识符

对变量或函数的命名即为标识符。标识符只可以由字母、数字和下划线组成,且不可以由数字开头。

数据类型

这里只考虑 C89 中的类型。并且数据占用长度按照 64 位 GNU 编译器的实现。

基本类型:

  • int,整型,4字节
  • short,短整型,2字节
  • long,长整型,8字节
  • char,字符型,1字节
  • float,单精度浮点型,4字节
  • double,双精度浮点型,8字节

派生类型:

  • 指针类型,表示一个内存地址,32位系统中为4字节,64位系统中为8字节
  • 数组类型,表示一个连续的内存空间
  • 结构体
  • 联合体

sizeof 运算符可以用于测量各个类型所占用的字节数,例如 sizeof(int) 返回 int 类型的数据占用的字节数。

在各种整型前加上 unsigned 关键字即可得到其无符号类型,这种数据只能是正数。

char 字符型的本质是占用一个字节的整型,范围是 0~127。所有整数可以参与的运算它也可以参与:

// 小写字母转大写字母
char func(char c)
{
    return c+'A'-'a';
}

运算符和表达式

%:取余运算

++、–:自增和自减运算

+±-可以放在变量前和变量后,单独存在时行为一致,结果参与运算时不一致。

int i = 3;
int j = i++;    // i=4,j=3
int i = 3;
int j = ++i;    // i=4,j=4

以i++和++i为例,如果i在前,那么先使用i的值,再做自增。++在前的话,i先自增,再使用i的值。即:谁在前,就先执行谁。

赋值传递:a=b=c,从右至左,先将c的值赋给b,再把b的值赋给a,最后a和b的值都等于赋值前的c。

不同类型的数据间计算:会先将低精度的类型转成高精度类型,随后参与运算,返回的结果一定是高精度的。如果高精度的类型是float类型,那么会被自动转为double(系统的所有浮点计算都按照double进行)。

布尔运算

C布尔运算符,即关系运算符。<、>、<=、>=、==、!=

C语言中没有布尔类型数据,所有的关系运算返回的都是整型数据。关系运算为真则返回1,为假则返回0。

对于使用关系运算的结构,如 if、while之类的,不为0则都是真,为0则是假。

例如:

// 死循环,100永远为真
while(100){}

逻辑运算,&&、||、!,与或非。

其中 && 和 || 一定是两个符号,单个 & 和 | 都是位运算,按位与 按位或。

不为0则都是真。例如,!100 的结果是0,!0的结果是1。

格式化输入输出

scanf,printf

参数都是 (const char *, args…)

第一个字符串是格式化字符串。其中包括占位符:

  • %d,整型
  • %ld,长整型
  • %f,单精度浮点型
  • %lf,双精度浮点型
  • %c,字符型
  • %s,字符串类型

有几个占位符,就需要有几个 args。scanf 由于需要给变量赋值,args 必须都是指针(注意,数组的本质也是指针,所以如果需要输入字符串,则无需加&取地址)。例如

int i;
scanf("%d", &i);

char str[30];
scanf("%s", str);

结构

switch 结构

switch(i)
{
    case 1:
    case 2:
    case 3:
    default:
}

会将 i 与 case 后的变量或常量逐个比较,如果相等就开始执行 case 后的语句,否则比较下一个 case。default 则默认直接执行。

如果一个 case 后没有 break 语句,那么就会接着执行下一个 case 或 default 的内容。

break 用于跳出 switch 结构。

循环结构

分为 while 循环、for 循环、do-while 循环。

while 循环:

首先判断 1 是否为真,如果符合就执行 2。执行 2 结束后会再次判断 1 是否为真。

while(1)
{
    2
}

for 循环:

首先执行1(1一共只会执行1次),接着判断2,如果2为真,执行4,再执行3,再判断2是否为真。

for(1;2;3)
{
    4
}

其中1、2、3都可以为空。但是分号不可省略。

do-while 循环:

首先执行1,再判断2是否为真,如果2为真,再次执行1。

do
{
    1
} while(2);

多个循环结构之间可以嵌套。

改变循环状态:break、continue。

break 直接跳出当前循环,continue 结束本次循环,进行下一次循环。

对于 for 循环来说:

for(1;2;3)
{
    4
}

如果 4 中执行到了 continue,则停止执行 4,开始执行 3,并进行下一次循环。

对于嵌套的循环,break 和 continue 都只能作用于当前最小范围的循环(最内层)。

break 同时也可以跳出 switch 语句,所以无论是 循环嵌套还是switch嵌套还是互相嵌套,break 跳出的永远是最内层的结构。

数组

普通数组

数组在定义时就必须指定数组的长度(所有维度)。C89 不允许使用变量作为数组的长度。

数组元素下标从 0 开始,每个元素都是同一个类型,占用一片连续的内存空间。

int a[10]; 声明的数组,其中符号 a 还可直接当作 int * 使用,表示一个指向 a[0] 的指针。这也是二维数组和二级指针的基础。

字符数组与字符串

C语言中,字符串是一种特殊的字符数组。字符串以 \0 字符作为自身的结尾。C 通过检测 \0 字符来判断字符串是否结束,而非是否已经到了字符串的结尾。

char str[30] = "Hello";

如上,虽然 str 字符数组的长度是30,但是 str 作为字符串的长度只有 5,占用了 str 数组中的 6 个字节(\0 也需要占用一个字节)。

使用 printf 的 %s 输出字符串时,也是输出到 \0 元素为止的。

C 提供了一些特殊的函数专门处理字符串,这些函数都在 string.h 中:

  • puts(字符数组),等价于 printf %s
  • gets,直接输入一个字符串,等价于 scanf %s
  • strcat(str1, str2),将 str2 接在 str1 的尾部,str1 必须足够大,可以容纳 str2,否则就会溢出。
  • strcpy(字符数组1, 字符串),将字符串复制到字符数组中。
  • strcmp(str1, str2),按照 ASCII 码序比较两个字符串,如果相等则返回0,如果 str1 的码序大于 str2 的码序则返回正数,否则返回负数。
  • strlen(str),返回字符串的长度(不是字符数组的!)

函数

参数的传递是值传递。基本类型传递的是它的值,函数的修改不会改变原变量。如果传递的是地址,那么所有的改变都会反映到原变量(除了修改地址值)

由于数组的本质是指针,将数组作为函数参数传递时,所有函数中对数组的修改都是对原数组的修改。

指针

指针操作

指针的本质,是一个整型数据,表示一个内存地址。

在定义指针变量时必须指定基类型,用来表示这个指针变量指向的数据类型。

声明指针类型:int *a,* 表示这是一个指针变量

相关操作:

&:取地址符,对一个普通变量取地址,返回指向这个变量的指针。

int i = 0;
int *p = &i;

*:解引用符(注意区别于声明中的那个*,那个只可以在类型中使用),对一个指针变量,获取它指向的数据。

int *p; // 假设已经指向了某个值
int i = *p;

当指针变量作为参数传递时,对其修改就会反映到原始变量。

int main()
{
    int i = 0;
    func(&i); // 执行后i变成2
}

void func(int *p)
{
    *p = 2; // 接引用并修改
}

指针与数组

对于一个数组 int a[10],a 同时可以作为一个指针。

int a[10];
a == &a[0] // 真

在引用数组元素时,指针可以进行运算。

int a[10];
int *p = &a[0];
p ++;   // 此时p指向a[1]

所以,获得一个数组a的第i个元素,有以下两种方式:

  • a[i]
  • *(a+i)

指针与二维数组

二维数组的本质是一维数组,这个一维数组中的每一个元素也是一个一维数组。

例如 int a[5][10],就是一个 5 个元素的一维数组,每个元素又都是一个 10 个元素的二维数组。

这样的话,如果将 a 当作指针的话,那就是一个二级指针。对 a 进行一次解引用之后,获取到的就是一个一级指针(一维数组),指向内层数组。

例如,我要通过指针的方式找到 a[3][5] 这个元素。

int a[5][10];
int *inner = *(a+3);
int res = *(inner+5);

这个例子中,inner 就指向了 a[3] 表示的数组,于是再做一次解引用就能拿到具体的值了。合在一起结果就是 *(*(a+3)+5)

函数指针不写了,但是函数是一等公民!

动态内存分配

直接在函数中声明的变量,是存放在内存的栈区的,函数的栈随着函数的结束而被回收。如果一个指针指向了在栈中分配的一个变量,那么在其所在的函数结束之后,再使用这个指针是不安全的。

所以可以通过动态内存分配的方式,在堆区上分配一块内存存储变量。这个变量就不会随着函数的结束而被回收。

动态内存分配通过 malloc、calloc、free、realloc 四个库函数实现,都在 stdlib.h 中。

malloc 函数原型为 void *malloc(unsigned int size),size 为要分配的字节数,返回的是指向这片区域的指针,这个指针无类型,在使用时需要强转成对应类型。如:

int *p = (int *)malloc(sizeof(int));

calloc 的功能类似于 malloc,原型为 void *calloc(unsigned n, unsigned size),这样就会分配 n*size 大小的内存空间(当然也可以给 malloc 直接传 n*size 也是一样的效果)。通常用在分配数组:

int *array = calloc(10, sizeof(int));

申请了 10 个 int 大小的空间,array 等效于一个 int array[10]

已经通过上面两种方式获取的内存空间,可以通过 realloc 改变大小:

int *array = calloc(10, sizeof(int));
// 扩充到 15 个 int 大小
p = realloc(p, 15*(sizeof(int)));

通过上面的方法分配的空间不会自动回收,如果大量申请可能会导致内存用尽。所以需要 free 函数手动回收,防止内存泄漏。

int *p = (int *)malloc(sizeof(int));
free(p); // 这之后p就无法再使用

结构体

结构体可以将多种不同类型的数据聚合成一个整体,作为一种新的类型。

struct Student
{
    int id;
    char name[30];
    int age;
};

这样 struct Student 就成了一种数据类型(注意不能漏掉 struct)

// 声明一个学生变量 stu1
struct Student stu1;

可以通过 typedef 关键字给结构体类型一个简短的别名:

typedef struct Student
{
    int id;
    char name[30];
    int age;
} student;

这样,student 和 struct Student 就是两个等价的类型。

对于一个结构体变量,可以通过 . 运算符获取其中的变量。例如声明的 stu1 变量,就可以通过 stu1.id 获取其 id。

声明的结构体类型,同样可以作为数组的元素类型使用。如 student stus[10];

对于一个结构体指针,如果想访问结构体中的变量,有两种方式,一种是直接接引用,然后用 . 访问:

student stu;
student *p = &stu;
*p.id   // 解引用再访问

或者,可以通过 -> 运算符直接访问:

student stu;
student *p = &stu;
p->id

结构体变量作为参数传递的时候,同样是传值的,内部的修改无法反映到外部。也可以通过传递结构体指针的方式传递,这样可以同步修改。

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值