C编程
C语言学习路线及重难点
- Mac系统及常用工具
- 常量、变量
- 运算符:自增、自减运算符 (重点)
- 分支结构
- 循环结构
- 循环:多重循环的嵌套、排序、查找 (重点)
- 函数:递归函数、递归调用
- 进制:各种进制转换、原码、反码、补码
- 数组:一维数组(重点)、二位数组、多维数组
- 指针:一级指针(重点)、多级指针、指针和数组、函数、结构体之间的关系
- 构造类型及结构体
- 预处理指令:有参宏及条件编译 (重点)
- const
Mac系统及常用工具
Mac系统是基于Unix内核的图形化操作系统,现行的最新的系统版本是macOS Catalina 10.15
(当下对应的最新版本iOS 13.3、watchOS 6.1.1、Xcode 11.3、Swift 5.1、TestFlight 2.6)
Mac软件安装,比较好用的网址
Mac下如何显示隐藏文件
- 显示Mac隐藏文件的命令:
defaults write com.apple.finder AppleShowAllFiles -bool true
- 隐藏Mac隐藏文件的命令:
defaults write com.apple.finder AppleShowAllFiles -bool false
常量、变量
常量,表示一些固定的数据,也就是不能改变的数据。
变量,表示的数据是可以经常修改的。作用域又分局部变量和全局变量
变量的存储分析
一个变量所占用的存储空间,不仅跟变量类型有关,而且还跟编译器环境有关系。同一种类型的变量,在不同编译器环境下所占用的存储空间又是不一样的
- 所占用字节数跟类型有关,也跟编译器环境有关
- 内存由大到小寻址
- 变量存储单元的第一个字节的地址就是该变量的地址
- 任何变量在内存中都是以二进制的形式存储。一个负数的二进制形式,其实就是对它的正数的二进制形式进行取反后再+1。(取反的意思就是0变1、1变0)
运算符
算术运算符(+,-,*,/,%)、关系运算符(>,<,>=,<=,==,!=)与逻辑运算符(&&,||,!)、按位运算符(&、|、^、~、<<、>>)
求余(%)运算符注意事项
m%n 求余,相当于m/n 获取余数
- 参与运算的m、n必须是整数
- m<n 结果是m 如:2 % 4 = 2;1 % 4 = 1;
- 运算结果的正负性取决于第一个运算数,跟后面的运算数无关-10 % 3 = -1;10 % -3 = 1
自增、自减运算符
- 前缀表达式:++x, --x;其中x表示变量名,先完成变量的自增自减1运算,再用x的值作为表达式的值;即“
先变后用
”,也就是变量的值先变,再用变量的值参与运算。 - 后缀表达式:x++, x--;先用x的当前值作为表达式的值,再进行自增自减1运算。即“
先用后变
”,也就是先用变量的值参与运算,变量的值再进行自增自减变化。
int num = 3 | ||
---|---|---|
++a | a++ | |
表达式值 | 4 | 3 |
变量num值 | 4 | 4 |
循环
多重循环的嵌套、排序、查找
- 选择排序
- 冒泡排序
- 折半查找
函数
C源程序是由函数组成,main函数是主函数,它可以调用其它函数,而不允许被其它函数调用
Xcode运行原理:
- Xcode4之前,Xcode是用
GCC
编译器来翻译代码,GCC
编译器是开源免费的,可以编译C/OC/C++/Java ... - Xcode4之后,用
LLVM
虚拟机来进行,里面Clang
前端就是专门用来编译代码
编译--->.o(目标文件)--->链接--->.out 执行
可以手动编译步骤,终端输入:
- 编译 命令:
cc -c main.c
(翻译我们自己的代码) - 链接 命令:
cc main.o
(将我们自己的代码和系统的以及其他依赖文件的代码组合在一起) - 执行 命令:
./a.out
进制
常见的进制:十进制、二进制、八进制、十六进制
- 十进制 0、1、2、3、4、5、6、7、8、9 逢十进一
输出占位符:%i
、%d
- 二进制 0、1 逢二进一
书写形式:需要以0b或者0B开头,比如0b101
; 输出占位符:c语言中没二进制输出的占位符 - 八进制 0、1、2、3、4、5、6、7 逢八进一
书写形式:在前面加个0,比如045
; 输出占位符:%o
- 十六进制 0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F 逢十六进一
书写形式:在前面加个0x或者0X,比如0x45
; 输出占位符:%x
了解进制间转换
例如: 二进制11转换为10进制
0b1011 = 1 * 2^0 + 1 * 2^1 + 0 * 2^2 + 1 * 2^3
= 1 * 1 + 1 * 2 + 0 * 4 + 1 * 8
= 1 + 2 + 0 + 8
= 11
原码、反码、补码
- 数据存储在内存中,都是存储的二进制
- 二进制又分为 原码、反码、补码
- 其实最终存储在内存中的是“补码”
例如:
9 ——> 存储在内存中 ——> 二进制 1001
9是整型 == int == 4个字节 == 1个字节是8位 == 整型有32位
则:
0000 0000 0000 0000 0000 0000 0000 1001 (原码)
正数的反码就是正数的原码
正数的补码就是正数的原码
总结:正数的原码反码补码都是一样的,三码合一
-9 ——> 存储在内存中 ——> 二进制
其实二进制的第一位是二进制的符号位,
如果该位是0代表这个数是一个正数
如果该位是1代表这个数是一个负数 则:
1000 0000 0000 0000 0000 0000 0000 1001(原码)
反码:符号位不变,其他位取反(0变1,1变0)
1111 1111 1111 1111 1111 1111 1111 0110(反码)
补码:反码 + 1 就是补码
1111 1111 1111 1111 1111 1111 1111 0111(补码)
为什么引入原码、反码、补码?
- 由于最高位是符号位,0代表正数,1代表负数,
那么如果直接存储原码,计算机在计算时需先判断最高位才能计算,效率比较低,
为了方便计算机计算,就有了原码、反码、补码;计算机只会做加法。
位运算符
- 位运算是指按二进制进行的运算,运行效率高。C语言提供了6个位操作运算符(&、|、^、~、<<、>>)。这些运算符只能用于整型操作数,即只能用于带符号或无符号的char、short、int与long类型。
-
& 按位与
- 只有对应的两个二进位均为1时,结果位才为1,否则为0。9 & 5=1;二进制1001 & 0101=0001
-
| 按位或
- 只要对应的二个二进位有一个为1时,结果位就为1,否则为0。9 | 5=13;二进制1001 | 0101=1101
-
^ 按位异或
- 当对应的二进位相异(不相同)时,结果为1,否则为0。9 ^ 5=12;二进制1001 ^ 0101=1100
-
~ 取反
- 各二进位进行取反(0变1,1变0)
-
<< 左移位运算符 (快速计算一个数乘以2的n次方)
- 把整数a的各二进位全部左移n位,高位丢弃,低位补0。左移n位其实就是乘以2的n次方
- 2<<1; //相当于 2 *= 2 // 4; 2<<2; //相当于 2 *= 2^2; // 8
-
>> 右移位运算符 (快速计算一个数除以2的n次方)
- 把整数a的各二进位全部右移n位,保持符号位不变。右移n位其实就是除以2的n次方
- 2>>1; //相当于 2 /= 2 // 1; 4>>2; //相当于 4 /= 2^2 // 1
类型说明符
- C语言提供了以下4种说明符:
-
short
短型 等价于short int
-
long
长型 等价于long int
-
signed
有符号型
有符号,也就是说最高位要当做符号位,所以包括正数、负数和0。其实int的最高位本来就是符号位,已经包括了正负数和0了,因此signed和int是一样的,signed等价于signed int,也等价于int。signed的取值范围是-2^31 ~ 2^31 - 1 -
unsigned
无符号型
无符号,也就是说最高位并不当做符号位,所以不包括负数。在64bit编译器环境下面,int占用4个字节(32bit),因此unsigned的取值范围是:0 ~ 2^32 - 1。打印无符号的变量,只能用%u
-
数组
数组遍历,长度计算方法为:
数组的长度 = 数组占用的总字节数 / 数组元素占用的字节数,如int length = sizeof(ages) / sizeof(int)
数组内部存储细节
- 计算机会给数组分配一块连续的存储空间
- 数组名代表数组的首地址,从首地址位置,依次存入数组的第1个、第2个....、第n个元素
- 每个元素占用相同的字节数(取决于数组类型)
- 并且数组中元素之间的地址是连续的
二维数组及多维数组
- 多维数组就是一个一维数组的每个元素又被声明为一维数组,从而构成二维数组. 可以说是特殊的一维数组。
- 示例:
int a[2][3]
- 可以看作由一维数组
a[0]
和一维数组a[1]
组成,这两个一维数组都包含了3个int类型的元素
字符串
char name[9] = "lnj"; //在内存中以“\0”结束, \0ASCII码值是0
char name1[9] = {'l','n','j','\0'};
char name2[9] = {'l','n','j',0};
char name3[9] = {'l','n','j'};//静态数组如果没有赋值默认就是0,0就相当于\0
- 字符串是位于双引号中的字符序列,在内存中以“\0”结束,所占字节比实际多一个
- 在C语言中没有专门的字符串变量,通常用一个字符数组来存放一个字符串
- 字符串常用函数:
strlen()
字符串长度函数
puts()
字符串输出函数
gets()
字符串输入函数
strcat()
字符串连接函数
strcpy()
字符串拷贝函数
strcmp()
字符串比较函数\
指针
什么是指针变量
- 在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。
- 严格意义上说,指针是一个地址,是一个常量
- 针变量是存放一个地址,是一个变量。
指针变量定义包括两个内容
- 指针类型说明,即定义变量为一个指针变量;
- 指针变量名;
- “*” 号表示定义的变量是指针变量,变量的值只能存放地址
- 在不是定义变量的时候 *是一个操作符,访问指针所指向存储空间
值传递、引用传递
- 基础数据类型作为函数参数是值传递,在函数中修改形参不会影响外面实参
- 数组作为函数参数是地址传递,如果是地址传递在函数中修改形参会影响外面实参
比如int nums[] = {1, 3}; 则 nums == &nums == &num[0]
- 结构体变量作为函数参数进行传递,其实传递的是全部成员的值,因此形参的改变不会影响到实参(成员变量是数组除外)
构造类型及结构体
在C语言中,构造类型有以下几种
- 数组类型
- 结构体类型
- 联合体(共用)类型
结构体存储原理
- 定义结构体类型并不会分配存储空间,只有定义结构体变量才会真正分配存储空间
-
struct
结构体名称并不是结构体的地址,&struct结构体的地址是他第0个属性的地址
结构体是如何分配存储空间 内存对齐
- 结构体会首先找到所有属性中占用内存空间最大的那个属性,然后按照该属性的倍数来分配存储空间
- 会从第0个属性开始分配存储,如果存储空间不够就会重新分配,如果分配空间有剩余,能容纳下后面属性时那么后面属性数据会存储到剩余的存储空间中
- 会从第0个属性开始分配存储,如果存储空间不够就会重新分配,并且会将当前属性的值直接存储到新分配的存储空间中,以前剩余的存储空间就不要了
结构体占用的内存空间是每个成员占用的字节数之和(考虑对齐问题)
预处理指令
C语言提供了多种预处理功能,如 宏定义、条件编译、文件包含
等。
宏定义(宏名一般用大写字母)
- 不带参数的宏定义
定义:#define 标识符 字符串
例如:#define PI 3.14
- 带参数的宏定义
定义:#define 宏名(形参) 字符串
例如:#define average(a, b) (a+b)/2
注意:- 宏名和参数列表之间不能有空格
- 定义宏时,一般用一个小括号括住字符串的参数
- 计算结果最好也用括号括起来
条件编译
#if-#else 条件编译指令
#if 常量表达式
..code1...
#else
..code2...
#endif
#ifndef 条件编译指令
#ifndef 标识符 程序段1
#else 程序段2
#endif
文件包含 #include
const
- 使用
const
修饰变量则可以让变量的值不能改变 - 常类型是指使用类型修饰符
const
说明的类型,常类型的变量或对象的值是不能被更新的
技巧
- 如果
const
在 *的左侧 表示值不能修改,但是指向可以改 - 如果
const
在 *的右侧 表示指向不能改,但是值可以改 - 如果在“*”的两侧都有
const
标识指向和值都不能改