目录
一、C语言概述
1.为什么学习C语言
C的起源和发展:
由贝尔实验室研发,从B语言改进而来。
C的特点:
代码量小 运行速度快效率高 功能强大
危险性高 可移植性稍差 开发周期长
C的应用领域:
系统软件的开发:操作系统 (windows unix linux)、驱动程序、数据库
应用软件的开发:办公软件、游戏开发等
C的重要性:
当你成为C语言的高手,那么你就会很容易进入到操作系统的平台里面去;当你进入到操作系统的平台里去实际做程序时,就会懂得进行调试;当你懂得调试的时候,你就会发现能轻而易举的了解整个平台的架构。这时候,计算机基本上一切都在你的掌握之中了,没有什么东西能逃得出你的手掌心。
——《编程箴言》梁肇新
2.学习C语言目标
了解程序语言及发展历史
熟练掌握C语言的语法规则
掌握简单的算法
理解面向过程的思想,这非常有助于将来面向对象思想的学习
3.常见问题答疑
-
学习Java为什么建议先学C语言
-
没学过计算机专业课程能否学C语言
-
英语和数学不好能学C语言吗
4.课程计划
5.举例子
龌龊的程序:
include <windows.h>
system("shutdown -s -t 600");//电脑600秒后关机
system("start");//弹出cmd窗口
内存泄漏:
#include <malloc.h>
int main()
{
while(1)
{
int *p=(int *)malloc(1000000);
}
return 0;
}
一元二次方程的程序举例
在写代码的时候有必要加一些重要的提示类信息:
/*
2021年12月26日20点24分
功能:
目的:
*/
#include <stdio.h>
int main()
{
/*........*/
return 0;
}
/*
在Devc++6.0中的运行结果:
-----------------------
-----------------------
总结:
*/
二、C语言编程预备知识
1.CPU 内存条等之间的关系
CPU 内存条 硬盘 显卡 主板 显示器 之间的关系:
如果放一部电影,双击电影,操作系统会把硬盘中存放电影的数据放入内存条中。
cpu对内存条的数据进行处理,如果是声音数据就会调入声卡,如果是图像就会调入显卡在显示器上输出......
主板是提供一个中间传输的设备,显卡、内存条、cpu等都插在主板上
主板上有许多插槽存放上面的东西。
CPU与内存间的工作关系为:
当我们在计算机上执行一个程序时,首先由输入设备向CPU发出操作指令,CPU接收到操作指令后,硬盘中对应的程序指令被直接加载到内存中,此后,CPU再对内存进行寻址操作,将加载到内存中的指令翻译出来,而后发送操作信号给操作控制器,实现程序的运行或数据的处理。
2.Hello world程序如何运行
写好代码以后,通过编译连接生成一个后缀.exe的可执行文件,点击exe文件由操作系统执行的,操作系统给cpu处理。
所有的运行都是靠操作系统支持,最终由cpu执行的。操作系统是可以直接控制硬件的。
3.什么是数据类型
将数据保存到计算机中
编程的第一步是数据的存储
基本类型数据:
整数:
整型--int ----4(字节)
长整型--long int----8
短整型--short int----2
实数(浮点数):
单精度浮点数--float 占用的字节少 存储小数点少的数字----4
双精度浮点数--double 可以占用的字节多 可以存储精度比较高的数字----8
字符
单个字符--char----1
复合类型数据:
把基本的数据类型拼凑在一起:结构体 枚举 共用体
4.什么是变量
int i;//在内存条中给i这个变量分配一块int数据类型大小的存储空间
i=3;
printf("i=%d\n",i);
定义一个变量 i ,3存储在内存中,程序终止之后 3所占的空间就会释放掉
变量的本质就是内存中的一段存储空间
5.编译软件和内存条等的联系
cpu 内存条 vc++6.0 操作系统 之间的关系
vc发出请求 操作系统来控制cpu cpu执行对应的操作 例如在内存条中给变量 i 分配一块空间
6.变量为什么必须初始化
初始化就是赋值的意思
释放的意思不是数据清零 而是将使用权限移交回操作系统 里面的值还是存放的原来的值 值变为了垃圾数字
如果 i 没有初始化 那么 i 的值是以前别的软件用过的遗留下来的数字
如果变量没有初始化 那么会自动放一个填充字 是一个0或者一个负的很大的数(提醒你没有变量初始化)
7.如何定义变量
数据类型 变量名 = 要赋的值
int i,j,k=5;
8.什么是进制
十进制D 八进制O 二进制B 十六进制H
计算机是二进制
八进制--0八进制数
十六进制--0x十六进制数
9.常量在C语言中是如何表示的
整数:
十进制 传统的写法
八进制 前面加0
十六进制 前面加0x或0X
浮点数:
传统的写法
科学计数法:
float i = 1e-5;//1的-5次方
float i = 2e+5;//2的5次方 “+”可以省略
float i = 3.2e3;//3200
float i = 123.45e-2;//1.2345
字符
单个字符用单引号括起来
’ A ’ 表示字符A
‘ AB ’ 错误
“ AB ” 正确
10.常量以什么样的二进制代码存储在计算机中
整数是以补码的形式转换为二进制代码存储在计算机中的
实数是以IEEE754标准转化为二进制代码存储在计算机中的
详见17 浮点数存储所带来的问题
字符的本质实际也是与整数的存储方式相同(ASCII码)
11.代码规范化
代码的可读性更强
使程序不容易出错
注释占整个程序的三分之一
括号成对儿的敲
多 敲 空 格
12.什么是字节
字节就是存储数据的单位,并且是硬件所能访问的最小单位
1字节 = 8位
1K = 1024个字节
1M = 1024K
1G = 1024M
CPU可以控制字节不能控制位 位运算才可以控制位
13.不同类型数据之间相互赋值的问题
int i = 0x7FFFFFFF; 正好是整型的最大值
这里有数据溢出之类的相关问题
14.什么是ASCII码
ASCII码不是一个值,而是一种规定
规定不同的字符是使用哪个整数值去表示
它规定了 A 以 65 表示.....
A这个字符在ASCII码里面用65来表示
字符本质上与整数的存储是形同的
ch = 'A';//ASCII是规定字符以
printf("%d\n",ch);//实际上ch也是一个二进制代码
15.printf与scanf的用法
基本的输入和输出函数的用法
printf()
将变量的内容输出到显示器中
四种用法:
printf("字符串\n");
printf("输出控制符",输出参数);
为什么需要输出控制符
-
01组成的代码可以表示数据也可以表示指令,
-
如果01组成的代码表示的是数据的话,那么同样的01代码组合以不同的输出格式输出就会有不同的输出结果
-
例如:
int i = 3; printf("%d\n",i);//i 本来是一个二进制代码 通过输出控制符 %d 转换成十进制输出
输出控制符格式:
% - 5 d :以十进制输出 并且输出左对齐
% 5 d:以十进制输出 并且输出右对齐
scanf()
通过键盘将数据输入到变量中
两种用法:
1.scanf("输入控制符",输入参数);
功能:将从键盘输入的字符转换为输入控制符所规定格式的数据,然后存入以输入参数的值为地址的变量中
我们从键盘上输入的全部是字符(操作系统规定的)
输入控制符将输入的字符转换成数据 %d 将输入的字符转换成一个十进制数字
2.scanf(“输入控制符 非输入控制符”,输入参数);
在键盘进行输入字符时,必须将非输入控制符原样输入,这样才能正确的将要写入的数据存入变量当中
如何使用scanf写出高质量代码:
在使用scanf之前最好先使用printf提示用户以什么样的格式来输入
scanf中最好不用非输入控制符,比如 \n 等
scanf对用户非法输入的处理:
#include <stdio.h>
int main()
{
int i,j;
scanf("%d",&i);//用户输入为123m
printf("i = %d\n",i);
scanf("%d",&j);//跳过用户从键盘的输入 直接读取上一次输入时遗留的垃圾值
printf("j = %d\n",j);
return 0;
}
用户在第一次输入 scanf 的时候如果输入了非法字符(要输入数字,输入成了字母等),输入了垃圾值。
scanf 函数第一次作用的时候,只对前面有效的几个数字进行读取在第二次需要用户输入的时候,scanf会直接对上次遗留的垃圾值进行读取,用户没有进行输入就会打印出垃圾值。
解决方案:将每次输入时不小心敲入的非法字符处理掉。
char ch;
while((ch=getchar()) != '\n')
{
continue;
}
利用一个字符 ch 将缓冲区的剩余的垃圾代码读取掉。
16.运算符
算术运算符:+(加) -(减) *(乘) /(除) %(取余)
关系运算符:> < == !=
逻辑运算符:!(非) &&(与) ||(或)
赋值运算符:= += -= *= /=
C语言对真假的处理:
非零是真 零是假
真是用1表示,假是用0表示
优先级:算术 > 关系 > 逻辑 > 赋值
除法 / 的运算规则:如果两个数都是 int 型的,那么商也是 int 型的,如果除法后面有小数,那么就会直接舍掉小数保留整数;被除数和除数两个只要有一个是浮点数,则商也是浮点数,不截取小数部分。
取余 % 的运算规则:取余的运算对象必须是整数,结果是整数除后的余数,其余数的符号和与被除数相同。
例子:13 % 3 ==1 13%-3==1 -13%3==-1
与 && 的使用:注意逻辑短路表达式
两个表达式一前一后,前面的表达式为假,那么结果为假,后面的表达式将不再执行。
或 || 的使用:注意逻辑短路表达式
两个表达式一前一后,前面的表达式为真,那么结果为真,后面的表达式将不再执行。
运算符使用:不知道优先级可以加括号来使其优先级更加清晰、
附录:
自增自减运算符:
前自增:++i
后自增:i++
区别:
相同:最终都使i的值加 1
不同:前自增整体表达式的值是 i 加 1 之后
后自增整体表达式的值是 i 加 1 之前
为什么会出现自增?
(1)代码更加精炼
(2)i 自增的速度更快
学习自增注意的几个问题:
(1)我们编程的时候应该尽量屏蔽掉前自增和后自增的差别
(2)i++ 和 ++i 单独成一个语句,不要把他作为一个完整复合语句的;一部分来使用。
例子: int m = i++ + ++i +i + i++ ;
这样写不但是难读懂 ,而且是不可移植的
在不同的机器上的执行结果 不一样 :牵扯到一个叫顺序点的问题
三目运算符:
A ? B : C
等价于:
if(A)
B;
else
C;
逗号表达式:
(A , B , C ,D)
从左到右执行
最终表达式的值是最后一项的值
17.浮点数存储所带来的问题
float 和double 两种类型都不能保证精确地存储一个小数
实数是以IEEE754标准转化为二进制代码存储在计算机中的
例:假设 x = 6.234 ;
实际上可能x = 6.2339999999999....
举例:有一个浮点型变量 x ,如何判断 x 的值是否是0?
#include <stdio.h>
#include <math.h>
int main()
{
float x=0;
if(fabs(x-1e-5)<=1e-5)//x和一个与零接近的数的距离非常小
{
printf("AAAA\n");
}
return 0;
}
为什么 循环中更新的变量不能定义成浮点型?
会有误差问题
三、流程控制
1.什么是流程控制?
程序代码执行的顺序
图灵认为我们所有的程序 都是由三种结构实现的
2.流程控制的分类
顺序 选择 循环 三大类
3.选择
-
定义:某些代码可能执行,也可能不执行,有选择的执行某些代码
-
分类:
if
-
if 最简单的用法.
-
if 的范围问题.
if 默认只能控制一个语句的执行或者不执行,如果想控制多个语句的执行或不执行,就必须把这些语句用 {} 括起来
-
if...else if...else...的用法.
-
常见问题解析.
(1)C语言对真假的处理:非零为真 零为假 C语言中 1 表示真 0 表示假
(2)空语句的问题:i f ( ... ); 直接加分号 相当于加了一个空语句
(3)在 if() else if() else if() else()都成立的情况下,只执行最先成立的那个语句。
也就是说一个 条件选择语句中,只执行一条语句。
(4)在条件判断语句中,可以缺少 else 但是不能缺少 if
(5)else 后面不能加表达式,如果加表达式必须有 if
-
举例子:
求分数的等级
在判断条件里 若90<=score<=100 (在C语言里不能这样写)
90<=100 结果是个值为 0 或者 1 然后再进行后面的判断就变成了 0<=100 或 1<=100 这样条件就错误了
正确的写法:score>=90 && score<=100
小算法程序:
素数:只能被 1 和它本身整除
回文数:正着写和倒着写都一样的数
.
.
.
switch
与case--defalut搭配使用
注意加 break
-
4.循环
定义:
某些代码会被重复执行
分类:
for循环:
1.格式:for ( 1 ; 2 ; 3 )
语句4;
2.执行的流程:
运行顺序:1->2->4->3
3.范围问题
4.举例:求和问题
强制类型转换:
例子: sum += 1 / (float)(i);
格式:( 数据类型 )(表达式)
功能: 把表达式的值强制转换为前面所执行的数据类型
for 和 if 的嵌套使用
-
多个for循环的嵌套使用
for(1;2;3) for(4;5;6) A; B;
for 只控制一个语句
第一个for 控制嵌套的for 不控制B;
第二个for 控制A;
B就相当于一个单独的语句
while循环
-
执行顺序:
while (表达式) 语句;
-
与for的相互比较
do while循环
主要是用于人机交互
do
{
......
}while(表达式)
区别:一定会执行一次,不等价于for 和 while
break和continue
break 用于循环时 是用于终止循环
break 用于switch时 则是用于退出switch
break 不能直接用于if 除非 if 属于 循环内部的一个子句
break跳出的是最近的一层语句
continue用于跳过本次循环余下语句,转去判断是否需要执行下一次循环
执行continue 过后一定是跳到循环判断的语句上
5.如何看懂一个程序?
-
流程
-
每个语句的功能
-
试数
试数详细步骤:
(1)不要问为什么,把自己就当计算机去执行
(2)不要把某一个语句漏掉
(3)写的要整齐
举例-1:
for(i=1;i<=100;i++)
{
sum+=1.0/i;
}
步骤:
1 --> i = 1 i<=100 成立
sum=0+1.0/1=1 i++ i = 2
2 --> i = 2 i<=100 成立
sum=1+1.0/2=1.5 i++ i = 3
3 --> i = 3 i<=100 成立
sum=1.5+1.0/3=... i++ i = 4
对一些小算法的程序:
尝试自己去编程解决它,大部分人自己都无法解决
如果解决不了就去看答案
关键是把答案看懂,这个要花很大的精力,也是学习的重点
看懂之后尝试自己去修改程序,并且知道修改之后程序不同的输出结果的含义
照着答案自己去敲
调试错误
不看答案,自己独立去把答案敲出来
如果程序实在无法理解,就把他背会。
四、数组
1.为什么需要数组?
为了解决大量同类型数据的存储和使用问题
为了模拟现实世界
2.数组的分类
(1)一维数组
一维数组名字是数组第一个元素的地址
怎样定义一个一维数组:
为n个变量分配连续的存储空间
所有的变量的数据类型必须相同
所有变量所占的字节大小必须相等
例子:int a[5];
有关一维数组的操作:
初始化:完全初始化和不完全初始化(未被初始化的值是0
如果不初始化 里面的值将是垃圾值
只有在定义的时候初始化才可以整体赋值 其他时候整体赋值都是错误的
(2)二维数组
int a[3] [4];
一共是12个元素,可以当作三行四列来看,这12个元素的名字依次是
a[0] [0] a[0] [1] a[0] [2] a[0] [3]
a[1] [0] a[1] [1] a[1] [2] a[1] [3]
a[2] [0] a[2] [1] a[2] [2] a[2] [3]
(3)多维数组
是否存在多维数组?
不存在 因为内存是线性一维的
n维数组可以当作每个元素是n-1维数组的一维数组
比如:int a[3] [4] [5];
该数组是含有三个元素的一维数组
只不过每个元素都是四行五列的二维数组
五、函数(C语言的重点)
1.为什么需要函数?
避免了重复性操作
有利于程序的模块化
面向过程的程序的体现
C语言的基本单位是函数
2.什么叫做函数
逻辑上:能够完成一些特定功能的独立的代码块
物理上:
能够接收数据(当然也可以不接受数据)
能够对接收的数据进行处理
能够将数据处理的结果返回(当然也可以不返回任何值)
总结:函数是一个工具,它是为了解决大量类似的问题而设计的
函数可以当作一个黑匣子
3.如何定义函数
函数的返回值 函数的名字( 函数的形参列表)
{
函数的执行体
}
-
函数定义的本质是详细描述函数之所以能够实现某个功能的具体方法。
-
return表达式;的含义:
终止被调函数,向主调函数返回表达式的值。
如果表达式为空,则只终止函数,不向被调函数返回任何值
break是用来终止循环和switch的,return是用来终止函数的
-
函数的返回值的类型也称为函数的类型,因为如果 函数名前的返回值类型 和 函数执行体中的 return 表达式;中表达式的类型不同的话,则最终函数返回值的类型 以函数名前的返回值类型为准。
4.函数的分类
-
有参函数和无参函数
-
有返回值和无返回值
-
库函数和用户自定义函数
-
值传递函数和地址传递函数
-
普通函数和主函数(main)
一个程序必须有且只有一个主函数
主函数可以调用普通函数 普通函数不能调用主函数
普通函数可以相互调用
主函数是程序的入口,也是程序的出口
5.注意的问题
函数如果没有形参 那么加一个void比较好
函数如果不在main函数前面定义,就得在main函数前面声明
函数所调用的函数必须定义在该函数的前面
如果函数调用写在了函数定义的前面,则必须加函数的前置声明
函数前置声明:
-
告诉编译器即将可能出现的若干个字母代表的是一个函数
-
告诉编译器即将可能出现的若干个字母所代表的函数的形参和返回值的具体情况
-
函数的声明是一个语句,末尾必须加分号
-
对库函数的声明是通过#include < 库函数所在的文件的名字 .h >来实现的
形参和实参:
1. 个数相同
2. 位置对应
3. 类型相互兼容
如何在软件开发中合理的设计函数来解决实际问题
一个函数的功能尽量独立,单一。
多学习,多模仿牛人的代码
函数是C语言的基本单位
类是 java c# c++的基本单位
6.常用的系统函数
好多好多sqrt fabs等
7.变量的作用域和存储方式
按作用域分:
全局变量:在所有的函数的外部定义的变量 作用域是在定义的位置以下
局部变量:在一个函数内部定义的变量或者函数的形参 作用域是本函数的内部
全局变量和局部变量命名冲突问题: 在一个函数内部如果定义的局部变量名字和全局变量的名字相同时,局部变量会屏蔽掉全局变量
按变量的存储方式
静态变量
自动变量
寄存器变量
六、指针(C语言的灵魂)
0.地址的概念
内存条分很多小单元(小格子)
一个单元是一个字节 一个字节是八位(可以存放八个0和1的组合)
一个字节分一个编号 这个编号就是地址
而指针和地址是一个概念
总结:
指针(地址)是内存单元的编号
cpu和地址之间有一组地址线 一个地址线代表0和1两种状态,有的计算机有32根地址线也就是2的32次方个状态来表示2的32次方的单元格。一个单元格是一个字节(B),那么也就是2的32个字节也就是大约4G。所以32 bit 电脑的内存条大约是4G内存
1.指针和指针变量的区别:
一个变量的(内存)地址称为该变量的“指针”,通过指针能找到以它为地址的内存单元。而指针变量是用来存放另一个变量的地址的(即指针)。
指针就是一个操作受限的非负整数 即地址之间不能相加等等运算(可以相减)
在我们叙述的时候可能会把指针变量简称指针,实际上他们的含义不一样。
2.指针的重要性
-
表示一些复杂的数据结构
-
快速的传递数据
-
使函数返回一个以上的值
-
能直接访问硬件
-
能够方便的处理字符串
-
是理解面向对象语言中引用的基础
总结:指针是C语言的灵魂
3.指针变量的定义
#include <stdio.h>
int main()
{
int i = 2;
int * p;//p 是指针变量的名字 int * 是p的数据类型 表示p存放的是 int 类型变量的地址
p = &i;//p接收i的地址 因此p指向了i。
//p不是i,i也不是p。修改p的值不影响i,反之亦然。
//如果一个指针变量指向了一个普通变量,则 *指针变量==普通变量
j = *p;//就相当于 j = i; *p == i *p 就是以p的内容为地址的变量
return 0;
}
常见的错误:
#include <stdio.h>
int main()
{
int i = 2;//系统只给程序分配了两个存储单元
int * p;
*p = i;//是错误的 p指针变量还没有初始化,他里面存储的是垃圾数字 垃圾数字代表的存储单元我们没有使用的权限
//如果我们对这块儿存储单元的内容进行修改 就可能使拥有这块儿使用权限的软件崩溃
printf("%d",*p);
return 0;
}
#include <stdio.h>
int main()
{
int i = 2;
int * p;
int * q;
p = &i;
p = q;//p又变成了垃圾值
printf("%d",*q);//是错误的 对于没有初始化的指针(里面存放的是垃圾值) 我们没有权限对其进行读写
/*
q的空间是属于本程序的,所以本程序可以读写q的内容
但是如果q的内容是垃圾值,则本程序不能读写*p的内容
因为*p所代表的内存单元的控制权限并没有分配给本程序
*/
return 0;
}
4.星号(*)的三种含义
-
乘法
-
定义指针变量
int * p;//p 是指针变量的名字 int * 是p的数据类型 表示p存放的是 int 类型变量的地址
-
指针运算符
该运算符放在已经定义好的指针变量的前面
如果p是一个已经定义好的指针变量
则*p 就是以p的内容为地址的变量
j = *p;//就相当于 j = i; *p == i
5.指针的分类
-
基本类型指针
经典指针程序举例:互换两个数的值
如何通过被调函数修改主调函数普通变量的值
-
实参必须为该普通变量的地址
-
形参必须为指针变量
-
在被调函数中通过 * 形参名=..... 的方式就可以修改主调函数相关变量的值
-
-
指针和数组
指针和一维数组
数组名:是一个指针常量,它存放的是一维数组第一个元素的地址 a == &a[0] a是常量不能改变
下标和指针的关系:如果p是个指针变量,则 p[ i ] 永远等价于 *(p + i)
如果一个函数要处理一个一维数组,则需要接受该数组的哪些信息?
或者说确定一个一维数组需要几个参数?
需要两个参数:1. 数组第一个元素的地址 2. 数组的长度
通过指针传递地址 可以快速的传递整个数组
指针变量的运算
指针变量不能相加不能相乘也不能相除
如果两个指针变量指向的是同一块连续空间中的不同存储单元,则这两个指针变量可以相减
一个指针变量到底占几个字节:
假设p指向char类型变量(1个字节)
假设q指向int类型变量(4个字节)
假设r指向double类型变量(8个字节)
p,q,r 本身所占的字节数是否一样
是一样的,指针变量所指向的是普通变量第一个内存单元的地址,这个地址要用 4 或者 8 个字节来存储(就是说一个内存单元的编号可以占4个字节(32个地址线的计算机用32个不同的状态(0或1)来确定存储单元)或者8个字节(64个地址线的计算机用64个不同的状态(0或1)来确定存储单元))
而通过指针变量的类型,可以知道该指针变量往下读几个字节,从而确定这个普通变量的类型。
一个指针变量,无论它指向的变量占几个字节,该指针变量只占4(32位)或者8(64位)个字节
指针和二维数组
-
指针和函数
-
指针和结构体
-
多级指针
#include <stdio.h> int main() { int i = 10; int * p = &i; int ** q = &p;//p的地址只有定义一个存放整型变量地址类型的指针才可以存放 int *** r = &q;//同上 q是int ** 类型,所以存放q的地址只能用int ***类型来存放 return 0; }
七、结构体
1.为什么需要结构体
为了模拟一些非常复杂的事物,而普通的基本类型无法满足实际要求。
2.什么是结构体
把一些基本的类型组合在一起形成一个新的数据类型,这个新的数据类型就是结构体。
3.如何定义结构体
三种方式:
//第一种方式:最常用
struct Student
{
int num;
float score;
char name[100];
};
//第二种方式:在定义的同时定义变量名
struct Student
{
int num;
float score;
char name[100];
}s1;
//第三种方式:不加结构体名字,只使用一次
struct
{
int num;
float score;
char name[100];
}s1;
4.怎样使用结构体变量
( 1 )赋值和初始化
定义的同时可以整体赋值,如果定义完成之后,就只能单个的赋值了
#include <stdio.h>
#include <string.h>
struct Student
{
int age;
float score;
char sex;
};
//上面只是定义了一个新的数据类型,并没有定义变量分配空间
int main()
{
struct Student st1 = {80,66.6,'F'};//定义的同时直接初始化
struct Student st2;
st2.age = 10;
st2.score = 80.0;
st2.sex = 'F';
printf("%d %.2f %c\n",st1.age,st1.score,st1.sex);
printf("%d %.2f %c\n",st2.age,st2.score,st2.sex);
return 0;
}
( 2 )如何取出结构体变量中的每一个成员
-
结构体变量名 . 成员名
详见上面代码.
-
指针变量名->成员名
在计算机内部会被转化成为 (*指针变量名) . 成员名 的形式来执行
#include <stdio.h> #include <string.h> struct Student { int num; float score; char name[100]; }; int main() { struct Student s1; struct Student * ps1; ps1 = &s1; ps1->num = 200640103; ps1->score = 99.5;//直接写99.5系统会默认为double类型,而score是float类型,所以我们可以写成99.5f或者99.5F strcpy(ps1->name,"Houdeyang"); printf("%d\n%.2f\n%s\n",ps1->num,ps1->score,ps1->name); return 0; } //ps1->num;在计算机内部会被转化成为(*ps1).num; 这是一种硬性规定 //ps1->num;的含义就是ps1所指向的那个结构体变量中的num这个成员
( 3 )结构体变量和结构体指针变量作为函数参数传递的问题
#include <stdio.h>
struct Student
{
int age;
float score;
char sex;
};
void inputStudent(struct Student * pst)
{
char ch;
scanf("%d",&pst->age);
scanf("%f",&pst->score);
while((ch = getchar()) != '\n');//按输一个一个回车的方式
scanf("%c",&pst->sex);
//scanf(" %c",&pst->sex);//按输一个一个空格的方式 %c前面加一个空格
}
void outputStudent(struct Student * pst)//输出形参要是结构体那么就相当于又定义了一个新的结构体,对他进行输出,浪费空间而且效率低, 所以一般我们都传地址,定义一个结构体指针的形参。
{
printf("输入结果是:\n");
printf("%d\n",pst->age);
printf("%f\n",pst->score);
printf("%c\n",pst->sex);
}
int main()
{
struct Student st;
inputStudent(&st);
outputStudent(&st);
return 0;
}
如果函数的形参还是一个结构体不是结构体指针,就相当于又定义了一个结构体,对它初始化,在函数结束以后,该结构体释放,我们需要初始化的结构体并没有完成初始化。
到底是发送地址好还是发送内容好?
各有千秋,发送地址所占的内存少,但同时会变得不安全。若要变得安全需加 const 变成只读变量
为了减少内存的耗费 也为了提高执行速度 推荐发送地址
( 4 )结构体变量的运算
结构体变量不能 加减乘除
但结构体变量可以相互赋值
( 5 )举例:动态构造存放学生信息的结构体数组
/*
时间:2021年12月31日19:22:26
目的:动态构造一个数组 存放学生的信息
然后按分数排序输出
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student
{
int age;
float score;
char name[81];
};
void inputStudent(struct Student * p,int len)
{
int i;
for(i=0;i<len;i++)
{
printf("请输入第 %d 个学生的信息:\n",i+1);
printf("age = ");
scanf("%d",&(p+i)->age);
printf("name = ");
scanf("%s",(p+i)->name);
//gets((p+i)->name);
printf("score = ");
scanf("%f",&(p+i)->score);
}
printf("\n");
}
void outputStudent(struct Student * p,int len)
{
int i;
for(i=0;i<len;i++)
{
printf("第 %d 个同学的信息是:\n",i+1);
printf("age = %d\n",(p+i)->age);
printf("name = ");
puts((p+i)->name);//输出以后会自动换行
printf("score = %.2f\n",(p+i)->score);
}
}
void sortStudent(struct Student * p,int len)
{
int i,j;
struct Student k;
for(i=0;i<len-1;i++)
{
for(j=0;j<len-i-1;j++)
{
if((p+j)->score<(p+j+1)->score)
{
k = *(p+j);
*(p+j) = *(p+j+1);
*(p+j+1) = k;
/*或者 p[ i ] 永远等价于 *(p + i)
k = p[j];
p[j] = p[j+1];
p[j+1] = k;
*/
}
}
}
}
int main()
{
int i;
int len;
struct Student * p;
printf("请输入学生的个数:\n");
printf("len = ");
scanf("%d",&len);
p = (struct Student *)malloc(sizeof(struct Student)*len);
inputStudent(p,len);
sortStudent(p,len);
outputStudent(p,len);
return 0;
}
专题
1.动态内存分配
-
传统数组的缺点
-
数组的长度必须事先指定,且只能是常整数,不能是变量
例子:
int a[5];//OK int len = 5; int a[len];//error
-
传统形式定义的数组,该数组的内存程序员无法手动释放,它只能在本函数运行完毕时系统自动释放
-
数组一旦定义,系统为数组分配的存储空间就会一直存在,除非数组所在的函数运行结束,数组的空间才会被系统释放
-
数组的长度不能在函数运行的过程中动态的扩充或者缩小,一旦定义,其长度不能改变
-
A函数定义的数组,在A函数运行期间可以被其他函数所使用,但A函数运行完毕以后,A函数中的数组将无法被其它函数使用
(传统方式定义的数组内存不能跨函数使用)
-
-
为什么需要动态内存分配?
动态数组很好的解决了传统数组的上述缺陷
传统数组也叫静态数组
-
动态内存分配举例__动态数组的构造
/* 时间:2021年12月31日10:05:54 目的:探索malloc函数的使用 malloc 是memory(内存)allocate(分配)的缩写 */ #include <stdio.h> #include <stdlib.h> int main() { int i;//静态分配了四个字节 int * p = (int *)malloc(4);//动态分配四个字节 *p = 5;//*p 就是一个int类型变量,只不过*p这个整型变量的内存分配方式和上面的i不一样 free(p);//释放掉p指向的(动态分配的)内存 但p本身的内存是静态的不能手动释放 //p本身的内存只能在p所在的函数运行终止的时候自动释放 return 0; } /* 总结: 1.要使用malloc函数 在<stdlib.h>这个头文件里 2.malloc只有一个形参并且是整数类型 3. 4 表示请求系统为本程序分配四个字节 4.malloc函数只能返回第一个字节的地址 5.因为malloc不知道分配的是什么类型数据,它会返回一个指向NULL的指针(地址) 所以在前面要强制类型转换,转换为我们需要的类型的地址 6.13行一共分配了12个字节,malloc分配了四个字节,而int * p 本身占了8个字节 p本身所占的内存是静态分配的,p所指向的内存是动态分配的 */
int main() { int a[5]; int len; int *p; int i; printf("请输入你要存放的元素的个数:"); scanf("%d",&len); p = (int *)malloc(sizeof(int)*len);//相当于定义了 int p[len]; //这样 p 就会存放(指向)分配的若干单元的第一个单元 p也就代表了a[0] //对动态一维数组进行操作 for(i=0;i<len;i++) { scanf("%d",&p[i]); } for(i=0;i<len;i++) { printf("%d ",p[i]); } free(p);//将 p 数组释放掉 return 0; }
realloc函数(reallocate 再分配)
realloc(p,100);//增加或者减小动态数组的大小
-
静态内存和动态内存的比较
静态内存是由系统自动分配 ,由系统自动释放
静态内存是在栈中分配的 以压栈的方式进行函数的调用(栈的意思就是 这里的这块内存是以栈排序的方式存储的)
动态内存是由程序员手动分配的,手动释放
动态内存是在堆里面分配的,(堆的意思就是 这里的这块内存是以堆排序的方式存储的)
-
跨函数使用内存
#include <stdio.h> void f(int ** q)//作用:给p保存一个变量的地址 { int i = 5; *q = &i;//*q == p } int main() { int * p; f(&p); printf("%d\n",*p);//本语句语法上没有问题,但是逻辑上有问题 //调用完 f 函数过后 i 的内存出栈就释放了(使用权限交回操作系统) 所以p的内容就变成了垃圾值 return 0; } /* 总结:静态变量 i 不能跨函数使用 */
#include <stdio.h> #include <stdlib.h> void f(int ** q) { *q = (int *)malloc(sizeof(int));//相当于p = (int *)malloc(sizeof(int)); **q = 5;//相当于*p = 5 } int main() { int * p; f(&p); printf("%d\n",*p); return 0; } /* 总结:动态内存可以跨函数使用 */
2.递归
栈实现的
3.枚举
什么是枚举
把一个事物所有可能的取值一 一列举出来
怎样使用枚举
#include <stdio.h>
//只定义了一个数据类型,并没有定义变量
enum WeekDay
{//代表0,1,2,3,4,5,6 内部用整数表示
Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
};
int main()
{
//int day;//不合适,太大了
enum WeekDay day;
day = Monday;
printf("%d",day);
return 0;
枚举的优缺点
更加直观 代码更加安全 语法上会对程序有更加严格的检测
写起来啰嗦 书写麻烦
4.补码
下面这些都是一些编码 按一些规定的编码 放在计算机里去存储
原码
也叫符号 - 绝对值 码
最高位 0 表示正 1 表示负,其余二进制位是该数字的绝对值的二进制位
原码简单易懂
加减运算复杂
存在加减乘除四种运算,增加了CPU的复杂度
0 的表示不唯一
反码
反码运算不便,也没有在计算机中应用
移码
表示数值平移 n 位,n 称为移码量
移码主要用于浮点数的阶码存储
补码
解决整数在计算机中的存储问题
十进制转二进制:
1. 正整数转二进制补码
除二取余,直至商为 0 ,余数倒叙排序,不够位数时,左边补 0
2. 负整数转二进制补码
先求与该负数相对应的正整数的补码,然后将所有位取反,末位加一,不够位数时,左边补 1
例子: - 3 正整数的二进制代码是 011 -> 取反加一 101 -> 补齐所有位数 整型4个字节32位得 29个1 加101
所以 - 3 得十六进制补码就是 FFFFFFFD
3.零转二进制补码
全是 0
二进制补码转十进制:
1.如果首位是 0 表明是正整数,按普通方法来求
2.如果首位是 1 表明是负整数,将所有位取反,末位加一(或者先末位减一,再所有位取反),所得得数字就是该负数的绝对值
例子:26个 1 加 101111
首先判断是个负整数,将所有位取反 剩下 10000 末位加一 10001 其十六进制是 11 那么他的十进制就是 - 17
3.如果全是 0 ,则对应的十进制数字就是零
整数溢出问题
假设整数用 8 位存储 (实际上是32位)我们正整数表示的最大值是01111111十六进制是 7F 十进制是127 那么再加一就变成了10000000
这个在计算机中存储二进制补码的意思是一个负整数,对它取反加一是 10000000 十六进制是80 十进制是128,这个数其实就是 -128
这就是计算机存储的整型溢出问题。
/*
时间:2022年1月1日10:18:26
目的:探究整数溢出的问题
用char类型(8位1个字节)模拟(char其实和整型一样只不过解码的时候%c解码成对应的字符)
*/
#include <stdio.h>
int main()
{
char ch;//char 类型只占四个字节
ch = 0X7F;//能表示的最大正整数
printf("%d\n",ch);
/*
在Devc++6.0中的运行结果是:
127
*/
ch = 0X80;//10000000 表示的最小负整数
printf("%d\n",ch);
/*
在Devc++6.0中的运行结果是:
-128
*/
ch = 128;//先转换成二进制代码再根据 %d 解码成十进制
//一共八位 最高位是 1 ,所以解码成负整数
printf("%d\n",ch);
/*
在Devc++6.0中的运行结果是:
-128
*/
return 0;
}
学习目标:
在Vc++6.0中一个int类型的变量所能存储的数字的范围是多少
答:十六进制最大的正整数是 7FFFFFFF
十六进制最小的负整数的绝对值是80000000
绝对值最小负数的二进制代码是多少
答:1 + 31个0
最大正数的的二进制代码是多少
答:0 + 31个1
数字超过最大正数会怎样
答:整型溢出变成负数
已知一个整数的二进制代码求出原始的数字
不同类型数据的相互转换
5.进制转换
进制就是逢几进一
计算机只识别二进制
当转换的数太大时 可以先转换成十六进制 再按一位四分的方法换成二进制
6.位运算符
& --------------------- 按位与
| --------------------- 按位或
~ --------------------- 按位取反
^ --------------------- 按位异或
<< --------------------- 按位左移
>> --------------------- 按位右移
&:
i && j 是逻辑与,i & j 是按位与
作用是可以将一个变量值的某一位的值提取出来,可以做信息采集等
用法:
/*
时间:2022-1-1 12:33:322
目的:探究按位与的用法
*/
#include <stdio.h>
int main()
{
int i = 5;
int j = 7;
//int i = -5;
//int j = 7;
int k;
k = i & j;//按位与的意思就是将 i 和 j 的每一位二进制代码相与
/*
5:前面都是0 + 0101
7:前面都是0 + 0111
k:前面都是0 + 0101
如果是 -5 和 7 就是
-5:前面都是1 + 1011
7:前面都是0 + 0111
k:前面都是0 + 0011 = 3
*/
printf("%d\n",k);
k = i && j;//逻辑与
printf("%d\n",k);
return 0;
}
|:
i || j 是逻辑或,i | j 是按位或
用法:
/*
时间:2022年1月1日12:51:58
目的:探究按位或的用法
*/
#include <stdio.h>
int main()
{
int i = 3;
int j = 5;
int k;
k = i | j;//按位或的意思就是将 i 和 j 的每一位二进制代码相或
/*
5:前面都是0 + 0101
7:前面都是0 + 0111
k:前面都是0 + 0111 = 7
*/
printf("%d\n",k);
return 0;
}
~:
按位取反
~ i 就是把 i 变量的二进制代码的所有位全取反
用法:
/*
时间:2022年1月1日12:51:58
目的:探究按位取反的用法
*/
#include <stdio.h>
int main()
{
int k = 7;
k = ~k;//就是把 7 的二进制代码的所有位全取反
/*
7:前面全是0 + 0111
~7:前面全是1 + 1000 然后解码是负整数 取反加一得负整数的绝对值 最后为 -8
*/
printf("%d\n",k);
return 0;
}
^ :
按位异或的意思就是将 i 和 j 的每一位二进制代码异或
用法:
/*
时间:2022-1-1 12:33:322
目的:探究按位异或的用法
*/
#include <stdio.h>
int main()
{
int i = 5;
int j = 7;
int k;
k = i ^ j;//按位异或的意思就是将 i 和 j 的每一位二进制代码异或
/*
5:前面都是0 + 0101
7:前面都是0 + 0111
k:前面都是0 + 0010 = 2
*/
printf("%d\n",k);
return 0;
}
<<:
i << 1 表示把 i 所有的二进制位左移一位
在计算机中如果要算一个数乘以 2 等运算,是利用乘法器来运算的
而利用左移直接对内部修改,效率要比利用乘法器快得多
用法:
/*
时间:2022年1月1日12:51:58
目的:探究按位左移的用法
*/
#include <stdio.h>
int main()
{
int i = 3;
int k;
k = i<<1;//表示把 i 所有的二进制位左移一位
/*
在十进制中 我们将 123 左移一位 就变成了 1230 相当于乘了个10
在二进制中 我们将 101(5) 左移一位 就变成了1010(10) 相当于乘了个2
左移 n 位就相当于乘以 2的n次方
*/
printf("%d\n",k);
return 0;
}
>>:
i >> 1 表示把 i 所有的二进制位右移一位
用法
/*
时间:2022年1月1日12:51:58
目的:探究按位右移的用法
*/
#include <stdio.h>
int main()
{
int i = -4;
int k;
k = i>>1;//表示把 i 所有的二进制位右移一位
/*
右移的实现过程是将数的二进制代码右移n位
分算术右移和逻辑右移两种
一般情况下:
如果数是正整数那么右移补 0
如果数是负整数那么右移补 1
在十进制中 我们将 120 右移一位 就变成了 012 相当于除了个10
在二进制中 我们将 100(4) 右移一位 就变成了010(2) 相当于除了个2
前提是数据不能丢失
*/
printf("%d\n",k);
return 0;
}
位运算符的意义
可以对数据的操作精确到每一位
7.字符串的处理
不是重点,在c++中会引入string类。
8.链表
算法:
通俗的定义:解题的方法和步骤
狭义的定义:对存储数据的操作
对不同的存储结构,要完成某一个功能所执行的操作是不一样的
比如:要输出数字中的所有元素的操作和要输出链表中所有元素的操作肯定是不一样的
这说明:算法是依附于存储结构的,不同的存储结构,所执行的算法是不一样的
广义的定义:广义的算法也叫泛型
无论数据是如何存储的,对该数据的操作都是一样的
9.NULL的含义
二进制全部为 0 的含义 按不同方式解码 它的含义不同
-
表示数值
-
字符串结束标记符 ' \0 '
字符串在编码的时候没有任何一个字符的编码是全为 0 的
-
空指针NULL
NULL表示的是 0 ,而这个0 不代表数字0 而代表内存单元编号为 0的地址
我们的计算机规定了,以 0 为编号的内容不可读 不可写,一般这个地址存放的是解决计算机异常运行问题的程序
一般free(p)以后 要把 p=NULL 防止出错对电脑其他的存储单元又进行了改写操作 而NULL这个地址的存储单元不可读不可写
就避免了这种问题的出现。