简介
跟着郝斌老师C语言自学教程,做的视频记录。
c语言的特点:
优点:代码量小,速度快,功能强大
缺点:危险性高,开发周期长,可移植性不强(很多机器不能跑)
c语言应用领域
系统软件开发
- 操作系统
- 驱动程序:主板程序、显卡驱动、摄像头驱动
- 数据库: DB2、Oracle、Sql server
应用软件开发
- 办公软件:WPS
- 图形图像多媒体:
- 嵌入式软件开发:智能手机、掌上电脑
- 游戏开发:2D、3D
参考资料
1
C语言关键字
auto | break | case | const |
---|---|---|---|
continue | default | do | double |
else | enum | extern | float |
for | goto | if | int |
long | register | return | short |
signed | sizeof | static | struct |
switch | typedef | unsigned | union |
void | volatile | while | char |
一个简单程序:
解一元二次方程
#include <stdio.h>
#include <math.h>
int main() {
int a, b, c;
float delta;
float x1, x2;
scanf_s("%d %d %d", &a, &b, &c);
delta = b * b - 4 * a * c;
if (delta > 0) {
x1 = (-b + sqrt(delta)) / (a * 2);
x2 = (-b - sqrt(delta)) / (a * 2);
printf("两个解:%f %f\n", x1,x2);
}
else if (delta == 0) {
x1 = (-b) / (2 * a);
x2 = x1;
printf("一个解:%f \n", x1);
}
else {
printf("无解\n");
}
return 0;
}
基本编程知识
1、cpu、内存条、硬盘、显卡、主板显示器 之间的关系
操作系统通过一些点击事件,先把硬盘中的东西,调用到内存条里面,cpu再去处理内存条里面的数据,处理的数据通过显卡在显示器中输出。
2、hello world 程序如何运行起来的
3、什么是数据类型
基本数据类型
- 整数
- 整型 – int (4个字节)
- 短整型 – short int (2个字节)
- 长整型 – long int (8个字节)
- 浮点数(实数)
单精度浮点数 – float (4个字节)
双精度浮点数 – double (8个字节)
float和double在计算机中,不能保证可以精确的存储一个小数
- 字符 – char (1个字节)
复合数据类型
- 结构体
- 枚举
- 共用体(用处不大,用于单片机,节省内存)
4、什么是变量
变量的本质,是内存中的一段内存空间
5、cpu、内存条、vc++6.0、操作系统 之间的关系
6、变量为什么必须得初始化
初始化就是赋值的意思,当变量未被初始化时,变量所占用的内存空间,会输出该内存空间原存放的数据(注:操作系统在回收内存空间时,并不会清空该内存空间中遗留下来的数据),会造成用户没发现该变量已经出现未知错误。
现在C语言在vs运行中,未初始化变量会直接报错。
7、如何定义变量
数据类型 变量名 = 要赋的值
等价于:
数据类型 变量名;
变量名 = 要赋的值;
例:
int i = 3; float j; j = 4.1;
8、 什么是进制
进制就是逢几进一,是一种计数的形式。
N进制就是逢N进一
汇编中:
数字后加
B
表示二进制数,(1101B) 加
O
表示八进制数,(573O) 加
D
表示十进制数,(12D) 加
H
表示十六进制数。(ab1eH)int i=20; printf("%d", i);// 十进制输出 printf("%x %X\n", i, i);// 十六进制输出 printf("%o \n", i); // 八进制输出
9、常量在c语言中是如何表示的
- 整数:
- 十进制:传统写法
- 十六进制:数字前面加
0x
或0X
- 八进制:数字前面加
0
- 浮点数
- 传统写法:float x = 3.2;
- 科学计数法:
- float x = 3.2e3; // x值为3200
- float x = 123.45e-2; // x值为1.2345
- 字符
- 单个字符用单引号:‘a’
- 字符串用双引号: “a” 代表 ‘a’和’\0’的组合
10、常量是以什么样的二进制代码存储在计算机中
整数以补码的形式转化为二进制代码存储在计算机中的,
实数以
IEEE754
标准转化为二进制代码存储在计算机中的,字符的本质与整数的存储方式相同。
11、程序代码规范化
定义变量=》变量初始化=》输出变量
使他人更清楚的看懂程序,使代码的可读性更强。
12、什么是字节
字节就是存储数据的单位,并且是硬件所能访问的最小单位。
一个字节=8位
1K = 1024个字节
1M= 1024K
1G= 1024M
13、不同类型数据之间相互赋值的问题
与补码有关
14、什么是ASCII
是一种规定,规定了不同的字符是使用哪个整数值去表示
15、字符的存储,字符本质上与整数的存储方式相同
printf的用法:输出控制符与输出参数
一一对应
输出控制符有:
%d %x %X %ld %f %lf %o %c %s %#X
(输出十六进制数123时,
显示器显示为:0x123,推荐使用)
数据类型
基本数据类型
- 整数
- 整型 – int (4个字节)
- 短整型 – short int (2个字节)
- 长整型 – long int (8个字节)
- 浮点数(实数)
- 单精度浮点数 – float (4个字节)
- 双精度浮点数 – double (8个字节)
- 字符 – char (1个字节)
复合数据类型
- 结构体
- 枚举
- 共用体(用处不大,用于单片机,节省内存)
数据类型之间的强制类型转换
- 格式:(数据类型)(表达式)
功能:把表达式的值强制转化为前面所执行的数据类型
例:
(int)(4.5+2.2);// 6 (float)(5); // 5.000000
分类:
隐式强制类型转换:
1/2.0;
显式强制类型转换:
(int)(4.2);
运算符和表达式
运算符
运算符 | 说明 |
---|---|
算术运算符 | + 、 - 、 * 、 / 、 % |
关系运算符 | > 、 >= 、 < 、 <= 、 != 、 == |
逻辑运算符 | ! (非)、&& (并且) 、` |
位运算符 | & 按位与、 ` |
赋值运算符 | = 、+= 、*= 、/= 、-= |
三目运算符 (条件运算符) | ?: |
优先级别 | 算术 > 关系 > 逻辑 > 赋值 |
逗号表达式 | a, b, c 从左到右执行 |
i = i*8; // A
i = i<<3;// B
// AB两个语句,在计算机中B语句执行速度更快。
流程控制*
1、什么是流程控制
是程序代码执行的顺序
2、流程控制的分类
顺序执行
选择执行
定义:某些代码可能执行,也可能不执行,有选择的执行某些代码
分类:
if
- 简单if
- if…else…
- if…else if…else…
switch
switch(表达式){ case 常量表达式1: 语句1; break; case 常量表达式2: 语句2; break; ... default: 语句n; break; }
循环执行
定义:某些代码会被重复执行
分类:
- for
- while
- do … while
break
和continue
- break:终止当前循环语句
- continue:跳过本次循环,继续进行下一次循环
嵌套
数组
一维数组
- 定义:为n个变量连续分配存储空间。
- 注:
- 所有变量数据类型必须相同
- 所有变量所占字节大小必须相等
// 完全初始化 int a[5] = {1,2,3,4,5}; // 不完全初始化 int a[5] = {1,2,3}; // 不初始化 int a[5]; // 清零 int a[5] = {0}; // 错误写法 int a[5]; a[5] = {1,2}; a[5] = 100;// wrong 最后为a[4]
二维数组
int a[2][3]={{1,2,3},{4,5,6}}; // 2行3列 // or int a[2][3]={1,2,3,4,5,6};
多维数组
计算机中不存在多维数组,
因为**内存是线性一维的**,
n维数组可以当作每个元素是n-1维数组的一维数组。
用途
排序、求最大/最小值、倒置、查找、插入、删除…
函数*
为什么需要函数
避免了重复性操作,
有利于程序的模块化
什么叫函数
逻辑上:能够完成特定功能的独立代码块(代码单元)
物理上:
- 能够接收数据
- 能够对接受的数据进行处理
- 能够将数据处理的结果返回
总结:
- 函数是一个工具,是为了解决大量类似问题而设计的工具
- 函数可以当作是一个黑匣子
如何定义函数
函数返回值 functionName(形参列表){
函数的执行体
return 表达式;
}
// void 函数没有return返回值
函数返回值类型和return 返回值类型相同,以函数返回值类型为基准。
函数分类
- 有参函数和无参函数
- 有返回值和无返回值
- 库函数和用户自定义函数
- 普通函数和主函数(main函数)
- 一个程序有且仅有一个main函数
- 普通函数可以相互调用
- 主函数是程序的入口和出口
- 主函数可以调用普通函数,但是普通函数不能调用主函数
函数的声明
函数是C语言的基本单位,类是Java、C#、C++的基本单位。
- 第一种写法:后置声明
#include <stdio.h>
void f(); // 注意分号
int main(){
f();
return 0;
}
void f(){
printf("hello world\n");
}
- 第二种写法:前置声明
#include <stdio.h>
void f(){
printf("hello world\n");
}
int main(){
f();
return 0;
}
注意的问题
return和break:
- return终止函数
- break终止循环,终止switch
函数是从上往下执行的。
#include <stdio.h> void g(){ f(); // wrong 先执行g() 函数时,还未定义f()函数。 } void f(){ printf("hello world"); } int main(){ g(); return 0; }
形参和实参
形参和实参必须
一一对应
,个数相同,数据类型相互兼容。形参:定义函数时,函数中定义形式参数列表中的参数。
实参:调用函数时,所传递的参数。
常用的系统函数
double sqrt(double x)
:求x平方根
int abs(int x)
:求x绝对值
double fabs(double x)
:求x的绝对值
递归
变量的作用域和存储方式
按作用域分:
- 全局变量
- 局部变量
按变量的存储方式
- 静态变量
- 自动变量
- 寄存器变量
指针
指针就是地址,地址就是指针。
地址就是内存单元的编号。
指针变量,是存放 地址 的变量。
指针和指针变量是两个不同的概念,
但是,通常我们叙述时会把指针变量简称为指针,实际它们的含义并不一样。
// 基本指针
int* p;// p是变量的名字,int* 表示p变量存放的是int类型变量的__地址__
int i = 3;
p = &i;
// 解释说明
/*
1、p保存了i的地址,因此p指向i
2、p不是i,i也不是p,更准确的说:修改p的值不影响i的值,修改i的值也不会影响p的值
3、如果一个指针变量指向某个普通变量,则
*指针变量 就完全等同于 普通变量
例:
如果p是指针变量,并且p存放了普通变量i的地址
则p指向了普通变量 i
*p 就完全等同于 i
或者说:在所有出现*p的地方都可以替换为i
在所有出现i的地方都可以替换为*p
*p就是以p的内容为地址的变量
*/
指针的重要性
指针用于表示一些复杂的数据结构
快速的传递数据,减少内存的损耗
使函数返回一个以上的值
能直接访问硬件
能够方便的处理字符串
使理解面向对象语言中引用的基础
指针是C语言的灵魂
指针的定义
地址:
- 内存单元的编号
- 从零开始的非负整数
指针:
- 指针就是地址,地址就是指针
- 指针变量就是 存放内存单元编号 的变量,或者说指针变量就是 存放地址 的变量
- 指针的本质就是一个操作受限的非负整数
指针的分类
- 基本类型指针
- 指针和数组
- 指针和函数
- 指针和结构体
- 多级指针
*
的含义
- 乘法
- 定义指针变量
- 指针运算符:该运算符放在已经定义好的指针变量。
如何通过被调函数修改主调函数普通变量的值
- 实参必须为该普通变量的地址
- 形参必须为指针变量
- 在被调函数中通过:
*形参名 = ...
的方式,就可以修改主调函数相关变量的值
指针和数组
指针和一维数组
一维数组名
一维数组名是一个指针
常量
,int a[10];其中数组名 a 是指针 常量
。存放的是一维数组第一个元素的地址,
输出 &a[0] 等价于 输出a
。
a是int* 类型
。下标和指针的关系
如果p是一个指针变量,则
p[i] 永远等价于 *(p+i)
int arr[5]={1,2,3,4,5}; int* parr = a; for(int i=0;i<5;i++){ printf("%d ,", arr[i]); printf("%d ,", *(parr + i)); printf("%d ,", parr[i]); printf("%d\n", *(arr + i)); } /* 结论 arr[i] = parr[i] = *(arr+i) = *(parr+i) */
指针变量的运算
指针变量不能相加,不能相乘,不能相除。
如果两个指针变量指向的是同一个连续空间中的不同存储单元,则这两个指针变量可以相减。
int a[5]; int* p; int* q; p = &a[1]; q = &a[4]; printf("%d\n", q-p); // 3
注:
int* p1; char* p2; double* p3; printf("%d,%d,%d\n",sizeof(p1),sizeof(p2),sizeof(p3)); // 输出结果相同,所占字节与系统的寻址能力有关。
多级指针
int i = 10;
int *p = &i;
int **q = &p;
int ***r = &q;
// ***r == i; // true
结构体
为什么需要结构体
为了表示一些复杂的事物,而普通的基本类型无法满足实际需求
什么叫结构体
把一些基本类型数据组合在一起形成的一个新的复合数据类型,这个叫做结构体。
如何定义结构体
3种方式
// 方式一,推荐使用
struct Student{
int age;
float score;
char sex;
};
// 方式二
struct Student2{
int age;
float score;
char sex;
} stu2;
// 方式三
struct{
int age;
float score;
char sex;
} stu3;
怎么使用结构体变量
- 赋值和初始化
struct Student{
int age;
float score;
char sex;
};
int main(void){
// 定义的同时,进行赋值
struct Student stu1 = {18,89.5,'F'};
// 先定义,再赋值
struct Student stu2;
stu2.age = 19;
stu2.score = 95.5;
stu2.sex = 'M';
return 0;
}
- 如何获取结构体变量中的每一个成员
1、
结构体变量名.成员名
2、
指针变量名->成员名
,在计算机内部转换为(*指针变量名).成员名
struct Student *ptr = &stu; ptr->age = 19; // 在计算机内部会转换成(*ptr).age ptr->score = 87.0; ptr->sex = 'M'; // ptr->age 等价于 (*ptr).age、stu.age;
- 结构体变量和结构体变量指针作为函数参数传递的问题
推荐使用结构体指针变量作为函数参数进行传递
#include <stdio.h>
#include <string.h>
struct Student{
int age;
char sex;
char name[100];
};
void inputStu(struct Student s);
void inputStu2(struct Student* pstu);
int main(void){
struct Student stu;
inputStu(stu); // 输入
inputStu2(&stu); // 发送地址
outputStu(std); // 输出
return 0;
}
// 本函数无法修改值,函数为 错误 使用。
void inputStu(struct Student s){
s.age = 10;
strcpy(s.name,"zhangsan");
s.sex = 'F';
}
// 使用结构体指针
void inputStu2(struct Student* pstu){
pstr->age = 10;
strcpy(pstr->name,"zhangsan");
pstr->sex = 'F';
}
// 输出
void outputStu(struct Student s){
printf(s.age,s.name,s.sex);
}
- 结构体变量的运算
结构体变量不能相加、不能相减、不能相互乘除,
可以相互赋值。
- 举例:动态构造存放学生信息的结构体数组
枚举
什么是枚举、怎样使用。
把一个事物所有可能的取值
一一列举出来
。
#include <stdio.h>
// 定义了一个数据类型,并没有定义变量,该数据类型的名字是enum WeekDay
enum WeekDay{
MonDay,TuesDay,WednesDay,ThursDay,FriDay,SaturDay,SunDay
};
int main(void){
enum WeekDay day = WednesDay;
printf("%d\n",day); // 2
}
枚举的优缺点
代码啊更安全
书写麻烦
专题
进制转换
计算机采用二进制由
莱布尼茨
提出。
进制就是逢几进一
在汇编中:
- 数组后加字母B,二进制
- 数组后加字母O,八进制
- 数组后加字母H,十六进制
补码
int类型变量
原码:
也叫符号-绝对值码,
最高位0表示正,1表示负,其余二进制位是该数字的绝对值的二进制位
优缺点:
- 原码简单易懂
- 加减运算复杂
- 存在加减乘除四种运算,增加了CPU的复杂度
- 零的表示不唯一。
反码:
反码运算不便,没有在计算机中应用
移码:
移码表示数值平移n位,n称为移码量,
移码主要用于,浮点数的阶码的存储。
**补码:**计算机存储的形式
已知十进制求二进制
- 求正整数的二进制
- 除2取余,直至商为零,余数倒叙排序
- 求负整数的二进制
- 先求与该负数相对应的补码,然后将所有位取反,末尾加一,不够位数时,左边补一
- 零的二进制,全是零
已知二进制求十进制
- 首位是0,表明是正整数,按普通方法求
- 首位是1,则表明是负整数,将所有位取反,末尾加一,所得数字就是该负数的绝对值
- 全是0,就是0
动态内存分配*
传统数组的缺点
数组长度必须事先指定,且只能是常整数,不能是变量
int a[5]; // ok int len = 5; int b[len]; // error
传统形式定义的数组,该数组的内存程序员无法手动释放。在一个函数运行期间,系统为该函数中数组所分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放。
数组的长度不能在函数运行的过程中动态的扩张或缩小。传统数组的长度一旦定义,其长度就不能再更改。
A函数定义的数组,在A函数运行期间可以被其他函数使用,但A函数运行完毕之后,A函数中的数组将无法再被其他函数使用。
为什么需要动态分配内存
动态数组很好的解决了传统数组的上面四个缺陷。
传统数组也叫静态数组。
动态内存分配举例——动态数组的构造
malloc()函数
#include <malloc.h>// 使用malloc函数需要添加这个头文件 // 语法 (指针变量类型)malloc(分配空间大小); // 例: int* p = (int*)malloc(16); free(p);// 释放p所指向的内存 /* malloc函数只有一个形参,并且形参是整型 16表示请求系统为本程序分配16个字节 malloc函数只能返回第一个字节的地址 p本身所占的内存是静态分配的,p所指向的内存是动态分配的。 */ // realloc函数 realloc(p,100);// 扩充 realloc(p,2);//缩小
静态内存和动态内存的比较
静态内存是由系统自动分配,由系统自动释放。
静态内存是在
栈
分配的。动态内存由程序员手动分配,手动释放。
动态内存是在
堆
分配的。
跨函数使用内存的问题
#include <stdio.h>
void f(int** q){
int i = 5;
// *q等价于p
// *q = i; // error
*q = &i;
}
int main(void){
int* p;
f(&p);
printf("%d\n", *p); // 逻辑上有问题,f()函数使用完之后,内存被释放,没有i值了。
}
#include <stdio.h>
#include <malloc.h>
void f(int** q){
*q = (int *)malloc(sizeof(int));
// 等价于 p = (int*)malloc(sizeof(int))
// q = 5;// error
// *q = 5;// p = 5;
// **q = 5;// *p = 5;
}
int main(void){
int *p;
f(&p);
printf("%d\n", *p); // ok,动态内存分配
return 0;
}
链表的使用
算法:
-
通俗定义:解题的方法和步骤
-
狭义定义:对存储数据的操作。
不同的存储结构,要完成某一个功能所执行的操作是不一样的。
算法是依附于存储结构的,不同的存储结构,所执行的算法是不一样的。
-
广义定义:广义的算法也叫
泛型
。无论数据是如何存储的,对该数据的操作都是一样的。
我们至少可以通过两种结构来存储数据
-
数组
-
优点:存取速度快
-
缺点:需要一个连续的很大的内存空间
插入和删除元素的效率很低
-
-
链表
-
专业术语:
-
头节点:
头节点的数据类型和首节点的类型是一模一样的,
头节点是首节点前面的那个节点,
头节点并不存放有效数据,
设置头节点的目的是为了方便对链表的操作。
-
头指针:存放头节点地址的指针变量。
-
首节点:存放第一个有效数据的节点。
-
尾节点:存放最后一个有效数据的节点。
-
-
优点:插入删除元素效率高,不需要一个连续的很大的内存
-
缺点:查找某个元素的位置效率底
-