C语言学习笔记
[TOC]
数据类型
1.基本类型
- 整数型:int、short int、long int、long long int
short int ≤ int ≤ long int≤ long long int
- 浮点型:float、double、long double
- 字符型:char
- 布尔型:_Bool
- 枚举型(不常用):enum
sizeof运算符
- 概念:sizeof运算符用于获得数据类型或表达式长度
- 用法:
sizeof(object); //查看对象长度 等价于sizeof object;
sizeof(type_name); //查看数据类型
类型限制符
- signed:表示变量带符号位(可存放负数)
- unsigned:表示变量不带符号位(不能存放负数,但意味着能存放更大的值)
- 用法:
- [signed] short [int]
- unsigned short [int]
- [signed] int
- unsigned int
- [signed] long [int]
- unsigned long [int]
- [signed] long long [int]
- unsigned long long [int]
2.指针类型
3.构造类型
数组类型
结构类型
联合类型
4.空类型
取值范围
1.单位
- 比特位(bit):CPU能读懂的最小单位
- 字节(Byte):内存机构的最小寻址单位
- 换算:1 Byte == 8 bit
2.进制换算
二进制 | 十进制 | 十六进制 |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
10 | 2 | 2 |
11 | 3 | 3 |
100 | 4 | 4 |
101 | 5 | 5 |
110 | 6 | 6 |
111 | 7 | 7 |
1000 | 8 | 8 |
1001 | 9 | 9 |
1010 | 10 | A |
1011 | 11 | B |
1100 | 12 | C |
1101 | 13 | D |
1110 | 14 | E |
1111 | 15 | F |
10000 | 16 | 10 |
10001 | 17 | 11 |
… | … | … |
11111111 | 255 | FF |
- 十进制二进制转换:2的n次方减1
3.符号位
存放signed的类型的储存单元中,左边第一位表示符号位,如果该位为1就表示该数为负,如果该位为0就表示该数为正
一个32位的整形变量,除去左边第一个符号位,剩下表示的值只有31个比特位
4.补码
正数的补码是该数的二进制形式
负数的补码通过几个步骤取得:
1.先取得该数的绝对值的二进制形式
2.将第一步的值按位取反(把1变为0,把0变成1)
3.将第二步的值加一
5.基本数据类型的取值范围
数据类型 | 字节数 | 取值范围 |
---|---|---|
char | 1 | -128 ~ 127 |
unsigned char | 1 | 0 ~ 255 |
short | 2 | -32768 ~ 32767 |
unsigned short | 2 | 0 ~ 65535 |
int | 4 | -2147483648 ~ 2147483647 |
unsigned int | 4 | 0 ~ 4294967295 |
long | 4 | -2147483648 ~ 2147483647 |
unsigned long | 4 | 0 ~ 4294967295 |
long long | 8 | -9223372036854775808 ~ 9223372036854775807 |
unsigned long long | 8 | 0 ~ 18446744073709551615 |
float | 4 | 1.7549 * 10^-38 ~ 3.40282 * 10^38 |
double | 8 | 2.22507 * 10^-308 ~ 1.79765 * 10^308 |
long double | 12 | 2.22507 * 10^-308 ~ 1.79765 * 10^108 |
字符与字符串
1.ASCII表
2.字符串
char 变量名[数量];
变量名[索引号] = 字符;
//声明字符串
char name[5];
//给字符串赋值
name[0] = 'N';
name[1] = 'i';
name[2] = 'n';
name[3] = 'e';
name[4] = 'K';
- 注意在末尾添加\0 , 否则会出现乱码
字符与字符串
运算符:
- C语言通过提供运算符来支持我们对数据的处理
1.算术运算符:
- 目:操作数的个数
- 表达式:用运算符和括号将操作数连接起来的式子
比如:1 + 1、a + b
- 类型转换:
(数据类型)值
2.关系运算符
- 使用关系运算符来比较两个数的大小关系
- 关系表达式:用关系运算符将两边的变量、数据或表达式连接起来,称之为关系表达式
如:1<2、a>b、(a=3) > (b=5)
关系表达式得到的值是一个逻辑值,即判断结果为“真”或“假”,如果结果为“真”,关系表达式的值为 1,如果为“假”,关系表达式的值则为 0。
3..逻辑运算符
C语言总共提供了三种逻辑运算符:
逻辑表达式:用逻辑运算符将两边的变量、数据或表达式连接起来,称之为逻辑表达式
如:3 > 1 && 1 < 2
- 注:关系表达式和逻辑表达式得到的值都是一个逻辑值,也就是表示真的 1 和表示假的 0。但是用于判断一个值是否为真时,以 0 表示假,以任何非 0 的数表示真。一个是编译系统告诉我们的结果,一个是我们让编译系统去判断的,两者方向不同。
4.运算符的优先级和结合性:
5.短路求值
短路求值又称最小化求值,是一种逻辑运算符的求值策略。只有当第一个运算数的值无法确定逻辑运算的结果时,才对第二个运算数进行求值。
C 语言对于逻辑与和逻辑或采用短路求值的方式。
- 例如:
#include <stdio.h>
int main()
{
int a = 3, b = 3;
(a = 0) && (b = 5);
printf("a = %d, b = %d\n", a, b);
(a = 1) || (b = 5);
printf("a = %d, b = %d\n", a, b);
return 0;
}
结果为:![](https://fishc.com.cn/forum.php?mod=image&aid=41970&size=400x300&key=3d6e6c06c1d0cd8f&type=1)
***
***
## **if语句**
### 语句1
```c
if(表达式){
... //逻辑值为真所执行的语句或程序块
}
语句2
if (表达式1)
{
…… // 逻辑值为真所执行的语句、程序块
}
else if (表达式2)
{
…… // 逻辑值为真所执行的语句、程序块
}
else if (表达式3)
{
…… // 逻辑值为真所执行的语句、程序块
}
switch语句与分支嵌套
1.switch语句
switch (表达式)
{
case 常量表达式1: 语句或程序块
case 常量表达式2: 语句或程序块
……
case 常量表达式n:语句或程序块
default: 语句或程序块
}
- 这里每个 case 后边的常量是匹配 switch 后边表达式的值
- case 后边必须跟一个常量值,而不能是一个范围
- 如果所有的 case 均没有匹配的,那么执行 default 的内容
- default 是可选的,如果没有 default,并且所有的 case 均不匹配,那么 switch 语句不执行任何动作
2. 使用 break 语句跳出
- switch 语句中的 case 和 default 事实上都是“标签”,用来标志一个位置而已。
- 当 switch 跳到某个位置之后,就会一直往下执行,所以我们这里还需要配合一个 break 语句,让代码在适当的位置跳出 switch。
3. 分支结构的嵌套
- 如果在一个 if 语句中包含另一个 if 语句,我们就称之为 if 语句的嵌套,也叫分支结构的嵌套。
4.else悬挂
- C 语言中有这样的规则,else 始终与同一对括号内最近的未匹配的 if 结合。
5. 等于号带来的问题
在 C 语言中使用等号(=)作为赋值运算,使用连续两个等号(==)作为比较运算。一般而言,赋值运算相对于比较运算出现得更频繁,因此字符较少的 = 就被赋予了更常用的含义——赋值操作。
此外,在 C 语言中赋值符号被作为一种操作符对待,因而重复进行赋值操作(如 a = b = c)可以很容易地书写,并且赋值操作还可以被嵌入到更大的表达式中。
但是,这种使用上的便利性可能导致一个潜在的问题:当程序员本意是在作比较运算时,却可能无意中误写成赋值运算
while语句与do while语句
1.循环结构
- 当我们需要重复执行同一段代码很多次的时候,就可以使用循环结构来解决。
2.while语句
while (表达式)
循环体
- while 语句的语法非常简单,只要表达式的值为真,那么就会不断执行循环体里边的语句或程序块。
3.do while 语句
do
循环体
while (表达式);
while 是先判断表达式,如果表达式结果为真,才执行循环体里边的内容;
而 do…while 则相反,先执行循环体的内容再判断表达式是否为真。
- 注意:do…while 语句在 while 后边一定要用分号(;)表示语句结束。
for语句与循环嵌套
1.循环三要点
- 初始化计数器
- 判断循环条件是否满足
- 更新计数器
2.for语句
for (表达式1; 表达式2; 表达式3)
循环体
- 三个表达式用分号隔开,其中:
表达式1是循环初始化表达式
表达式2是循环条件表达式
表达式3是循环调整表达式
- 特点:
for 语句的表达式1,表达式2和表达式3都可以按照需要进行省略(但分号不能省):
for ( ; 表达式2; 表达式3)
for (表达式1; 表达式2; )
for (表达式1; ; )
for ( ; ; )
……
- 注意:如果目的不是特别明确,建议不要这么做,因为程序的可读性会因此而降低!
3. 循环嵌套
循环结构跟分支结构一样,都可以实现嵌套。
对于嵌套的循环结构,执行顺序是从内到外:先执行内层循环,再执行外层循环。
break语句和continue语句
1.break 语句
在循环体中,如果我们想要让程序在中途跳出循环,那么我们同样可以使用 break 语句来实现。
执行 break 语句,直接跳出循环体。
对于嵌套循环来说,break 语句只负责跳出所在的那一层循环,要跳出外层循环则需要再布置一个 break 语句才行。
2.continue语句
当满足某个条件的时候,跳过本轮循环的内容,直接开始下一轮循环。这时候我们应该使用 continue 语句。
当执行到 continue 语句的时候,循环体的剩余部分将被忽略,直接进入下一轮循环。
对于嵌套循环来说,continue 语句跟 break 语句是一样的:它们都只能作用于一层循环体。
3. for 语句和 while 语句执行过程的区别
for 语句和 while语句执行过程是有区别的,它们的区别在于出现 continue 语句时。
在 for 语句中,continue语句跳过循环的剩余部分,直接回到调整部分。
在 while 语句中,调整部分是循环体的一部分,因此 continue 语句会把它也跳过。
数组
1.数组的含义
- 数组是储存同类型数据的地方
2.定义数组
- 类型 数组名[常量表达式] 例如:
int a[6]; double b[4];
3.访问
数组名[下标]
a[1]; b[3];
第一个元素的下标是0而不是1
4.数组的初始化
在定义数组的同时对其各个元素进行赋值,称之为数组的初始化
第一种:将所有元素统一初始化为某个值
int a[9] = {9};
第二种:赋不同值,用逗号隔开
int a[9] = {1,2,3,4,5,6,7,8,9};
第三种:只给一部分赋值,其余自动初始化为0
int a[10] = {1, 2, 3, 4, 5, 6}; // 表示为前边 6 个元素赋值,后边 4 个元素系统自动初始化为 0
第四种:可以只给出各个元素的值,而不指定数组的长度
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
第五种:C99 增加了一种新特性——指定初始化的元素。这样就可以只对数组中的某些指定元素进行初始化赋值,而未被赋值的元素自动初始化为 0
int a[10] = {[3] = 3, [5] = 5, [8] = [8]}; // 编译的时候记得加上–std=c99选项
字符串处理函数
字符数组
- 两种方式存放和表达字符串:1.字符串常量2.字符类型的数组
- 字符数组
可以先定义指定长度的字符数组,然后再给每个元素单独赋值:
int main
{
char str[10];
str[0] = 'N';
str[1] = 'i';
str[2] = 'n';
str[3] = 'e';
str[4] = 's';
str[5] = '\0';
……
}
还可以直接在定义的时候对字符数组进行初始化,这样会方便很多:
int main
{
// 初始化字符数组的每个元素
char str1[10] = {'N', 'i', 'n', 'e', 's', '\0'};
// 可以不写元素的个数,因为编译器会自动计算
char str3[] = {'N', 'i', 'n', 'e', 's', '\0'};
// 使用字符串常量初始化字符数组
char str4[] = {"Nines"};
// 使用字符串常量初始化,可以省略大括号
char str5[] = "Nines";
}
字符串处理函数
- 获取字符串的长度:strlen 函数
拷贝字符串:strcpy 函数和 strncpy 函数
连接字符串:strcat 函数和 strncat 函数
比较字符串:strcmp 函数和 strncmp 函数
二维数组
1.二维数组的定义
- 定义二维数组的方法跟一维数组相似,使用方括号指定每个维度的元素个数:
类型 数组名[常量表达式][常量表达式]
注意:这里需要强调的是几行几列我们是从概念模型上来看的,也就是说,这样来看待二维数组,我们可以更容易理解。但从物理模型上看,无论是二维数组还是更多维的数组,在内存中仍然是以线性的方式存储的。
2.二维数组的访问
- 数组名[下标]:
a[0][0]; // 访问a数组中第1行第1列的元素 b[1][3]; // 访问b数组中第2行第4列的元素
- 要防止数组越界
3.二维数组的初始化
由于二维数组在内存中是线性存放的,因此可以将数据写在一个花括号内
为了更直观地表示元素的分布,可以用大括号将每一行的元素括起来
二维数组也可以仅对部分元素赋初值
C99 同样增加了一种新特性:指定初始化的元素。这样就可以只对数组中的某些指定元素进行初始化赋值,而未被赋值的元素自动初始化为 0
int a[3][4] = {[0][0] = 1, [1][1] = 2, [2][2] = 3};
二维数组也可以让编译器根据元素的数量计算数组的长度。但只有第 1 维的元素个数可以不写,其他维度必须写上
指针
1.内存存放变量
- 通过变量名对变量进行访问和存储是为了方便程序员而设计的,其实在内存中完全没有存储变量名的必要。因为编译器知道具体每一个变量名对应的存放地址,所以当你读取某个变量的时候,编译器就会找到变量名所在的地址,并根据变量的类型读取相应范围的数据。
2.指针和指针变量
- 通常我们所说的指针,就是地址的意思。C 语言中有专门的指针变量用于存放指针,跟普通变量不同,指针变量存储的是一个地址。
- 指针变量也有类型,它的类型就是存放的地址指向的数据类型。
3.定义指针变量
- 定义指针变量跟普通变量十分相似,只是中间多了一个星号(*)
int *ptr;
- 左侧的数据类型表示指针变量中存放的地址指向的内存单元的数据类型
4.取地址运算符和取值运算符
如果需要获取某个变量的地址,可以使用取地址运算符(&)
如果需要访问指针变量指向的数据,可以使用取值运算符(*)
注意:取值运算符跟定义指针用的都是星号(*),这属于符号的重用,在不同的地方有不同的意义:在定义时表示定义一个指针变量;在其他位置表示获取指针变量指向的变量的值
避免访问未初始化指针
指针和数组
1.概念
- 数组不是指针
- 数组名是数组第一个元素的地址,也是数组的首地址
2.指向数组的指针:
int a[] = {1, 2, 3, 4, 5}; int *p; p = a; // 语句1 p = &a[0]; // 语句2
因为数组名即数组第一个元素的地址,所以语句 1 和语句 2 是等价的,都是将数组 a 的首地址存放到指针变量 p 中。
3. 指针的运算
当指针指向数组元素的时候,我们可以对指针变量进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后第 n 个元素。比如 p+1 表示指向 p 指针指向的元素的下一个元素;p-1 则表示指向上一个元素。
注意:p+1 并不是简单地将地址加1,而是指向数组的下一个元素
指针数组和数组指针
1. 指针和数组的区别
- 指针是左值
- 而数组名只是一个地址常量,它不可以被修改,所以数组名不是左值。
2. 指针数组
int *p1[5];
- 指针数组是一个数组,每个数组元素存放一个指针变量。
3.数组指针
int (*p2)[5];
- 数组指针是一个指针,它指向的是一个数组。
指针和二维数组
在 C 语言中,二维数组的实现,只是简单地通过“线性扩展”的方式进行。
要用指针来指向二维数组,需要使用数组指针的形式。
void指针和NULL指针
1.void类型
- void 即的字面意思是“无类型”,定义变量的时候,我们通过类型来决定该变量所占的内存空间。
2.void指针
- void 指针我们把它称之为通用指针,就是可以指向任意类型的数据。也就是说,任何类型的指针都可以赋值给 void 指针。
不要直接对 void 指针进行解引用,因为编译器不知道它所指向的数据类型
使用 void 指针一定要小心,由于 void 指针可以包罗万象的特性,间接使得不同类型的指针转换变为合法
3.NULL指针
如果一个指针不指向任何数据,我们就称之为空指针,用 NULL 表示。
#define NULL ((void *)0)
地址 0 通常是一个不被使用的地址。所以,如果一个指针指向 NULL,那么就意味着该指针不指向任何东西。
当你还不清楚要将指针初始化为什么地址时,请将它初始化 NULL;在对指针进行解引用时,先检查该指针是否为 NULL。这种策略可以为你今后编写大型程序节省大量的调试时间。