c语言程序设计第五章手写笔记,C语言笔记

C语言

- 编译:

将.c源文件中的代码编译为对应的二进制指令。

`cc -c .c文件名`

如果一切正常的情况下,会生成一个.o文件,叫做目标文件

这个目标文件存储的是.c文件对应的二进制代码

>注意:编译器在编译的时候,先检查拟定的源文件中的代码是否符合C语言的语法规范,如果符合才能生成目标文件,如果不符合就会报错,不会生成目标文件

- 链接:

目标文件无法交给CPU直接执行,因为.o文件中只是存储的.c的二进制代码,一个可以被执行的文件中必须还要有执行代码才可以。链接的事情之一:为.o目标文件添加启动代码

`cc .o文件名`

如果一切正常的情况下,会生成一个a.out文件,是我们最终编写的程序,可以直接执行

如果程序中使用到了框架中的函数或者类.那么在链接的时候,就必须要告诉编译器 去那1个框架中找这个函数或者类.

`cc xx.o -framework 框架名称.`

- 执行

`./a.out`

补充:

1. 返回值代表程序的结束状态.

0 代表正常结束 非0代表非正常结束.

2. 参数

argv数组的第0个元素的值 是这个正在运行的程序的路径.

argc代表的是数组的长度.

3. 你要清楚一件事情.

我们写的程序可以在终端中运行. 运行的时候可以为程序传递一些数据.

传递的方式:

程序名 数据1 数据2 数据3 .....

在程序的内部如何拿到.

argc代表用户传递的数据的个数.

argv数组中每1个元素存储的就是用户传递过来的数据.

第0个元素是程序的路径.

- 数据类型

printf高级用法:

%md 位数不足m位,空格补足

%0md 位数不足m位,0补足

%.nf 四舍五入,保留n位小数,0补足(float)

%.nlf 四舍五入,保留n位小数,0补足(double)

赋值数据类型与变量声明类型不同,则会自动类型转换:

当变量的类型为int的时候:

1).赋值超出了int的范围,C系统会将数据转换为一个随机的int数据

2).赋值超出了int的范围太多,此时自动类型转换无能为力,编译器会直接报语法错误

3).赋值的数据类型是一个实型的小数,C系统会直接截取整数部分

当变量的类型为float的时候:

float a = 12.f;

1).赋值的数据是一个double类型的小数,C系统会将这个double类型的小数转换为float

1).赋值的数据是一个int类型的整数,C系统会将这个int类型的整数转换为float小数,直接加一个.0就可以

当变量的类型为double的时候:

double a = 12.0;

1).赋值的数据是一个float类型的小数,C系统会将这个float类型的小数转换为double,占据8个字节

1).赋值的数据是一个int类型的整数,C系统会将这个int类型的整数转换为double小数,直接加一个.0就可以

当变量的类型为char的时候:

char a = 'a';

ASCII码: 每一个字符数据都有一个与之对应的整数, A-65 a-97 0-48

为char变量赋值的时候,可以直接赋值ASCII码

char a = 97;

当我们为char变量赋值一个整数的时候,其实赋值的是以这个整数位ASCII码所对应的字符数据

- scanf函数的使用:

scanf("格式控制符", 变量地址列表);

步骤:

a.在格式控制符中使用占位符来要求用户输入一个指定类型的数据

b.在后面写上要将用户输入的数据存储在哪个变量的地址

使用&就可以获取到变量的地址

int num = 0;

scanf("%d", &num);

scanf函数的执行原理:

scanf函数是一个阻塞式的函数,当CPU执行到这个scanf函数的时候,CPU的执行就会暂停,不会继续往下执行了,并等待用户输入数据,当用户s输入完毕数据,按下回车键表示输入完毕,此时,就回将用户输入的数据赋值给后面指定的变量,然后再继续往下执行

// 注意:

1).scanf函数是输入不是输出,所以不要在后面加\n

2).scanf函数后面的参数是写变量的地址 而不是变量,使用&取变量的地址

3).如果程序正在运行,想要重新运行程序 要先将这个正在运行的程序停止

4).存储用户输入的数据的变量的类型要和scanf函数的占位符对应

用户在输入多个数据的时候,默认的分隔符号是空格或者回车

自定义分隔符:

在格式控制字符串中可以自定义多个输入数据的分隔符.

scanf("%d-%d-%d", &num, &boyNum, &avg);

代表输入3个数据,这三个数据用-分隔开

注意: 一旦指定了分隔符,那就必须使用指定的分隔符,空格回车则无效;一次输入的多个数据,只能是数(整型和浮点型),如果有char混合输入,就会出问题

数据输入完成后,并不是将这个数据直接赋值给变量,而是存入缓冲区

在执行scanf函数的时候,会先检查缓冲区中是否有数据,如果缓冲区中没有数据,那么就会让用户输入数据

当从缓冲区中拿数据的时候,如果要拿到的数据类型是整型或者实型,如果拿到的是空格 回车 Tab键 就会被自动忽略,继续往下拿

如果拿到的数据类型是字符型,不会忽略任何数据.

所以,当数字和字符混合输入的时候,字符的接收就有可能会出问题

解决方案: 在输入字符之前,将缓冲区中的数据全部清空

char color = "a";

rewind(stdin); // 将缓冲区中的数据全部清空

scanf("%c", &color);

- 算术运算符

1).算术表达式都有一个结果,一般处理方式是声明一个变量将这个表达式的结果存储起来

我们必须知道算术表达式的结果的类型,只有知道类型才可以声明一个对应的类型的变量来保存这个结果.

2).如果参与算术表达式的操作数的类型都是一致的,那么这个算术表达式的结果的类型就是这个l类型

10/4 记住,这个算术表达式的操作数都是int的,所以这个表达式的结果是2,不是2.5 ,所以正确的方式是使用int变量保存结果

如果为了得到结果是2.5, 可以将任意一个操作数的类型改为double(或乘以1.0)

10%3(求模运算)

可以用来判断1个数是否为另外一个数的倍数,或1个数能否被另一个数整除

实型数据无法参与求模运算,因为没有意义

m%n的结果一定在0-(n-1)之间

3).如果参与算术表达式的操作数的类型不一致,那么这个算术表达式的结果的类型就是范围最大的那个类型

int < float < double

4).当表达式中的操作数是一个char数据的时候,会先将这个char数据的ASCII码找出来代替,然后参与算术运算

- 自增自减运算

++i 先运算再赋值 i++ 先赋值再运算

--i I--

- 逗号表达式

int num = (i++,j++,++k,i+j+k);

从头到尾的去执行每一个子表达式,最后一个子表达式的结果就是整个表达式的结果

- 比较表达式

使用int类型的数据来表示真假

0代表假 非0代表真

- 逻辑运算符

断路问题:

int i = 0;

int res = i++ > 0 && ++i < 3

printf("%d", i);

打印结果 i=2 ????

逻辑表达式在执行的时候,是先计算左边的条件表达式的结果,再计算右边的表达式的结果

*****当是&&的时候,如果左边的不成立,则可以确定整个逻辑表达式的结果为0,而这个时候,右边的条件根本就不会去判断了,所以右边的代码也不会再执行

*****当是||的时候,如果左边的成立,则可以确定整个逻辑表达式的结果为1,而这个时候,右边的条件根本就不会去判断了,所以右边的代码也不会再执行

优先级:

! > && > ||

*/

- 函数

- goto函数; 不建议使用,因为它不安全,容易造成死循环,除非在特别确定的情况下不会造成d死循环,就可以使用

且只能在当前函数中跳转

取标签名下面的代码不能是声明代码,如果非要声明的话,可以先写个printf函数

loop:(标签名:)

printf("sss");

goto loop;

- 全局变量:

如果全局变量的类型是char类型,并且也没有初始化,那么系统就会自动给这个char变量赋值'\0'

\0 代表一个不可见的字符,这个字符的ASCII码就是0

全局变量与局部变量的异同点:

同: 都是变量,都是在内存中开辟一块空间来存储数据

异:1.声明的位置不同(函数内部与外部) 2.作用域不同

3.默认值不同

局部:默认值是一个垃圾数,是个随机数

全局:默认值为0

4.创建和回收的时间不同

1. 预处理指令/预处理代码

C语言的代码主要分为两类,

1).C代码 之前学习的代码都叫C代码

2).预处理代码 在编译之前执行的 以#开头的代码叫预处理代码

2. 手写一个C程序的步骤

1).在.c的源文件中写上符合C语言规范的源代码

2).编译 使用cc -c 指令将C语言的源代码编译为.o的目标文件

a.先检查源文件中的代码是否符合语法规范

YES 生成目标文件 NO 报错

3).链接 使用cc 指令 将目标文件链接生成一个可执行文件

a.为目标文件添加启动代码

4).执行可执行文件

.c源文件 ->执行.c文件中的预处理代码 ->检查语法 -> 编译成.o目标文件 ->链接生成可执行文件 ->执行

3.预处理指令

1).分类:

a.文件包含指令 #include

b.宏定义 #define

c.条件编译指令 #if

2).特点:

a.都是以#开头

b.预处理指令的后面没有分号

c.在编译的时候,在检查语法之间执行

4. 文件包含指令 #include

1).作用:将指定的文件的内容拷贝到指令的地方

2).语法:

#include "文件路径" 直接目录查找

#include 从编译器目录查找

- int num = 11;

if ((num & 1) == 0) {

num是偶数

}

- 数组:

1).特点:

a.可以存储多个数组

b.存储的多个数据的类型相同

c.长度固定

d.存储在数据中的数据方便管理

2).语法:

元素类型 数组名[长度];

int arr[2];

arr的类型是int数组

3).存储/取出 数据,使用下标

4).遍历

5).注意点:

a.数组的长度

b.数组元素的默认值 0

c.初始化

int arr[] = {10,293,10};

int arr[] = {0};

当声明数组的同时使用大括弧初始化数组的时候 数组的长度不能是一个变量

3.数组在内存中的存储

数组的地址

数组名 == 数组地址 == 数组的低字节的地址 == 数组的第0个元素的地址 == 数组的第0个元素的低字节地址

数组的长度

sizeof(数组名) / 每一个元素占用的字节数

sizeof(arr) / sizeof(arr[0])

4.数组与函数

当数组作为函数的参数的时候

传递实参数组的时候会丢失数组的长度,所以在函数的内部使用sizeof计算数组的长度的时候是计算不出来的

再多传一个参数 让调用者将数组的长度一并传递过来

5.数组的算法

a.求最大,最小值,累积和.平均值

b.判断指定的数据是否包含在数组中

c.找出指定的数据在数组中的下标

d.产生不重复的随机数

int balls[] = {0};

for (int i = 0; i < 6; i++) {

loop:

printf("");

int num = arc4random_uniform(33)+1;

int rtes = containsObject(balls, 6, num);

if(rtes == 1) {

goto loop;

}else {

balls[i] = num;

}

}

int containsObject(int arr[], int length, int num) {

for (int i = 0; i < length; i++) {

if (arr[i] == num) {

return 1;

}

}

return 0;

}

e.

// 选择排序

for (int i = 0; i < len - 1; i++) {

for (int j = i + 1; j < len; j++) {

if (arr[i] < arr[j]) {

int temp = arr[i];

arr[i] = arr[j];

arr[j] = temp;

}

}

}

// 冒泡排序

for (int i = 0; i < len - 1; i++) {

for (int j = 0; j < len - 1 - i; j++) {

if (arr[j] < arr[j+1]) {

int temp = arr[j];

arr[j] = arr[j+1];

arr[j+1] = temp;

}

}

}

二分查找:

// 前提:arr是一个有序数组

int min = 0;

int max = len - 1;

int num = 11;

while(min

int mid = min+max/2;

if (arr[mid] == num) {

return num;

} else if(arr[mid] > num) {

max = mid-1;

} else {

min = mid+1;

}

}

- 二维数组与函数

1).当二维数组作为函数的参数的时候,丢失行数和列数, 应同时传入行数和列数

2).形参二维数组: 行数可以省略 列数不可省略

实现二维数组: 行数任意 但是列数需保持一致

void test(int rows, int cols, int arr[][cols]);

- 字符串

1).字符串数据的每一个字符存储在字符数组中,会自动追加一个'\0'结束

char name[] = "jack"; ======= char name[5]

2).用printf("%s", name); 输出字符串

3).输入字符串

scanf("%s", name);

a.不安全,当数组的长度不够的时候就会崩溃

b.空格问题, 输入空格会有\0问题

4).长度计算

a.不能用sizeof字符数组计算,因为这样得到的是字符数组的长度

b.从第一个字节开始计数,直到遇见'\0'为止

sizeof 返回定义arr数组时,编译器为其分配的数组空间大小,不关心里面存了多少数据。

strlen 只关心存储的数据内容,不关心空间的大小和类型。

5).字符串的常用函数

puts(); // 输入 不安全

gets(); // 输出 不安全

strlen(); // 长度

strcmp(); // 比较两个字符串设这两个字符串为str1,str2,若str1=str2,则返回零

strcpy(); // 把str2的字符串复制到str1中,这两个都是地址

strcat(); // 拼接字符串

- 指针变量:专门用来存储地址的变量, 专门用来存储另外一个变量的地址的,可以说指针变量指向了另外一个变量

变量的地址就叫做指针, 指针就是地址, 地址就是指针

int num = 20;

int *p1 = &num;

p1操作的是p1这个指针变量,可以取p1的值, 也可以为p1赋值

&p1拿到的是p1的地址

- 野指针: 没有初始化的指针变量,值为垃圾值,随机数

访问野指针指向的变量会报错,BAD_ACCESS错误

- NULL 代表指针变量不指向内存中的任何地址,NULL完全等价于0,所以也可以赋值0

int *p = NULL;

int *p = 0;

此时依然不可以访问,否则会报错

函数的参数:

1.当函数的参数类型是int 等等普通类型的时候,

参数传递是值传递

在函数的内部改变形参变量的值,对实参变量没有丝毫影响

2.当函数的参数类型是数组的时候,

参数传递是地址传递

在函数的内部改变参数数组的元素的时候,其实改变的就是实参数组的元素

3.当函数的参数类型是指针的时候,

在函数的内部访问参数指针指向的变量的时候,其实改变的就是实参

无论指针是什么类型,在内存中都占据8个字节

- 多级指针

声明二级指针: **p

三级指针: ***p

// 指针遍历数组

int arr[7] = {10,20,30,40,50,60,70};

int *p = arr;

for (int i = 0; i < 7; i++) {

printf("%d\n", *(p+i));

printf("%d\n", *(arr+i));

}

- 当数组作为函数的参数的时候

在声明这个参数数组的时候,它不是去创建1个数组,而是去创建一个用来存储地址的指针变量

如果我们为函数写了一个数组作为参数

其实编译器在编译的时候已经把这个数组换成了指针

所以,在声明参数的时候不是创建数组,而是创建一个存储数组地址的指针变量

这 也是为什么我们通过sizeof计算参数数组得到的永远都是8

void test(int arr[], int len);

替换为 void test(int *arr);

- 索引的本质

1).指针变量可以使用中括弧,在中括弧中写上下标来访问数据

2).p1[n]; 前提是p1是一个指针变量

完全等价于 *(p1+n)

3).只要是指针都可以使用中括弧下标,就相当于是访问指针指向的变量

arr[0] = 100;// *(arr+0)

arr[1] = 200;// *(arr+1)

两个指针变量相减 就是用在数组中,判断两个元素之间相差多少个元素

- 字符串的两种存储方式

1).使用字符数组来存储

char name[] = "jack";

2).使用字符指针存储

char *name = "jack";

区别:

1).存储的结构不同

2).可变与不可变

- 字符串的恒定性

- fputs(); f-->File

作用: 将字符串数组 输出到 指定的流中

流: 标准输出流->控制台

文件流->磁盘上的文件

使用格式:

fputs(要输出的字符串, 指定的流);

1.标准输出流(控制台) : stdout

char *name = "sds";

fputs(name, stdout);

2. 将字符串存储到文件中

a.要先声明一个文件指针,指向磁盘上的文件

fopen函数可以创建一个指向文件的指针

fopen(文件的路径(代表创建的指针指向这个文件), 操作文件的模式(对文件做什么操作));

操作文件的模式: "w"-->write

"r"-->read

"a"-->append 追加数据

当操作模式是w的时候,如果文件不存在则创建这个文件

如果文件存在则会将原来的文件替换掉

当操作模式是a的时候,如果文件不存在则创建这个文件

如果文件存在则会追加

b.使用fputs函数将字符串写入到指定的文件流中

fputs(字符串, 文件指针);

c.写完之后,一定要使用fclose函数结束这个文件

FILE* pFile = fopen("/User/Desktop/abc.txt", "w");

char *name = "ss";

fputs(name, pFile);

fclose(pFile);

printf("写入成功");

- fgets函数

作用: 从指定的流中读取字符串

fgets(要将字符串存储到哪一个数组中, 最多接收多少个长度的字符串(n , 最多n-1个,最后一个自动\0), 文件的路径);

1.标准输出流(控制台) : stdin

char input[5];

fgets(input, 5, stdin);

size_t len = strlen(input);

if (input[len - 1] == '\n') {

input[len - 1] = '\0';

}

2. 将字符串存储到文件中

FILE* pFile = fopen("/User/Desktop/abc.txt", "r");

char name[50];

fgets(name, 50, pFile);

fclose(pFile);

printf("写入成功");

- 字符串数组

1).字符的二维数组 char name[][4]

2).字符指针数组 char *name[4]

- const关键字

用来修饰变量,被const修饰的变量具备一定程度上的不可变性,叫做"只读变量"

const int num = 10;

const int arr[4] = {10,20,30,40}; 数组的元素不可更改

const int *p1 = &num; 无法通过指针p1去修改指针指向的变量的值,但是如果直接去操作变量是可以的,

但是指针变量的值可以改,可以把另一个变量的地址赋值给指针

*p1 = 100; ❎

int age = 20;

p1 = &age; ✅

int const *p1 = &num; 效果同上

int * const p1 = &num;

p1的值不能修改,但是可以通过p1去修改p1指向的变量的值

int const * const p1 = &num;

p1的值不能修改,也不可以用p1去修改p1指向的变量的值

使用场景:

1).const的特点:

被const修饰的变量,是只读变量,只能取值,不能改值

所以,const变量的值至始至终都不会发生变化

2).当某些数据是固定的,在整个程序运行期间都不会变化,而且也不允许他人去更改,此时就用const

3).当函数的参数是一个指针的时候,函数的内部有可能会改变实参变量的值

void test(const int arr[]);

- 内存管理

1.内存的五大区域

栈: 局部变量

堆: 堆区中的字节空间允许程序员是手动的申请

BSS段: 未初始化的全局变量和静态变量

数据段: 已初始化的全部变量,静态变量,常量数据

代码段: 存储代码段落

2.如何向堆区申请字节空间来使用

1).我们在堆区中申请的字节空间,如果我们不主动释放,那么系统是不会释放的,除非程序结束了

2).在堆中申请字节空间的步骤: 申请->使用->释放

#include

- 向堆内存中申请连续的参数个数字节空间:

malloc(n);

(size_t == unsigned long)

返回值: void * 代表没有类型的指针

返回的是创建的空间中第一个字节的地址,地址是没有类型的

* 在堆区申请的字节空间是从低地址向高地址分配

每次申请的字节地址都是从0开始,每一次申请的字节空间不一定挨在一起

但是,每一次申请的指定个字节一定是连续的

* 在堆区申请的字节,里面是有值的,值是垃圾值,不会自动清零

* 在堆区申请字节空间的时候有可能会申请失败,失败时返回NULL值,最好判断一下

int *p1 = malloc(4);

if(p1) {

// 申请成功

}

* 申请的空间使用完毕后,一定要记得释放

释放申请的堆空间: free(指针);

- 向堆内存中申请指定个数字节空间:

calloc(多少个单位, 每一个单位多少字节);

int *p1 = calloc(3, sizeof(int));

if (p1) {}

相较malloc,申请完之后,系统会将字节中的数据清零

- realloc 扩容

如果原来的剩余空间不够扩容,则拷贝数据后自动释放,重新找一块足够的空间申请

- 指向函数的指针

1).可以定义一个指针指向一个函数,来使用这个指针间接调用这个函数

2).指向函数的指针的声明

拷贝函数头,去掉函数名 用小括弧代替,里面写上 *指针名

3).初始化

函数名就代表函数的地址,直接将函数名赋值给指针

函数声明:

void text () {

}

改为:

void (*pFunc) () = test;

4).使用

a. pFunc();

b. (*pFunc)();

- 结构体

作用:创建一个数据类型,这个数据类型的变量是由多个其他普通类型的小变量联合而成的

struct 新类型名称(首字母x大写)

{

成员;

};

struct Student

{

char *name;

int age;

};

声明结构体类型的变量:

struct 新类型名称 变量名

结构体变量的初始化:

struct Student std1;

std1.name = "jack";

std1.age = 18;

也可以: 按顺序初始化成员变量

struct Student std1 = {"jack", 18};

也可以:

struct Student std1 = {.age = 18, .name = "jack"};

结构体成员变量的默认值:

声明一个结构体变量,如果没有为这个结构体变量的成员赋值,那么成员有值,是垃圾值

只要在声明结构体变量的同时只要初始化一个成员,其他的成员就会被自动初始化为0

作用域:一般情况下,都是定义在函数外面

结构体变量之间的相互赋值:

相同结构体绝对可以: 将源结构体中的每一个变量拷贝一份赋值给目标结构体

struct Student std1 = {"jack", 18};

struct Student std2 = std1;

- 结构体数组

struct 结构体类型名称 数组名称[数组长度];

struct Student arr[5];

arr[0] = (struct Student){"jack", 18};

或:

struct Student arr[5] = {{"jack", 18},

{"jack", 18}

};

结构体数组长度计算:

sizeod(arr) / sizeof(struct Student);

结构体指针的声明:

struct 结构体类型名称 * 指针名;

struct Student * pStu1 = &std1;

指针间接访问结构体变量的值:

(*pStu1).age = 19;

pStu1->age = 19;

结构体嵌套:

struct Student

{

char *name;

int age;

struct Date birthday;

};

struct Date

{

int year;

int month;

int date;

}

struct Student stu1 = {"jack", 19, {2000, 10, 20}};

结构体作为函数的参数: "值传递"

int isAdult(struct Student stu){

if (stu.age >=18)

return 1;

}else {

return 0;

}

结构体作为函数的返回值:

struct Student getAStudent(){

struct Student stu1 = {"jack", 19, {2000, 10, 20}};

return stu1;

}

// 返回指针

struct Student *getAStudent(){

struct Student *p1 = calloc(1, sizeof(struct Student));

p1->name = "jack";

...

return p1;

}

- 枚举

创建一个数据类型,这个数据类型的变量的取值被限定

enum 新类型

{

枚举值1, 枚举值2..... (每一个枚举值都有一个对应的整数,从0开始,依次递增)

};

enum ButtonType

{

ButtonTypeNormal,

ButtonTypePress,

ButtonTypeDisabled

};

声明枚举类型的变量:

enum 新类型名称 变量名

enum ButtonType type = ButtonTypeNormal;

- typedef 将一个已经存在的数据类型取一个别名

- 预处理指令

#define 与 typedef 的区别:

1).#define是一个预处理指令,在预编译的时候执行,把宏名换成宏值

typedef是一个C代码,在运行的时候才会执行

2).#define可以将任意的C代码取一个标识名

typedef只能为数据类型取名

#if

#elseif

#endif

#ifdef

#ifndef

- static修饰局部变量

1).修饰局部变量,这个变量就叫做静态变量

2).静态变量不再存储在栈区域,而是存储在常量区

3).第一次执行这个函数的时候,就会将这个静态变量声明在常量区,当函数执行完毕后,这个静态变量不会被回收,仍然存在

后面再去执行的时候,声明静态变量的这句话会直接略过不会再执行

- extern不能修饰局部变量

- static修饰全局变量\函数:只能在当前模块中使用

- extern修饰全局变量\函数:可以跨模块使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值