郝斌C语言(180集自学教程)

零散知识笔记(老师讲的基本都在这里了)
算法:
解体的方法和步骤
如何看懂一个程序,分三布:
1、流程
2、每个语句的功能
3、试数
1、判断一个数字是否为素数
只能被1或本身整除的数为素数
2、判断一个属是否为回文数
正反写都是一个数 如 1221 1331.。。。
如何看懂一个程序,分三步:
1、流程
2、每一个语句的功能
3、试数
如何学习一些小算法的程序【如何掌握一个程序】
1、尝试自己去编程解决它,大部分人都无法自己解决
2、如果解决不了就看答案,把答案看懂,这个要费很大精力,也是学习重点
3、看懂之后尝试自己去修改程序,并且知修改之后程序的 不同输出结果的含义
4、按着答案去敲,调试错误
5、不看答案,自己独立把答案敲出来;
终极秘诀:
如果实在无法彻底理解,就把他背会;
强制类型转化
格式:
(数据类型)(表达式)
功能:
把表达式的值强制转化为前面所执行的数据类型
例子:
(int)(4.5+2.2) 最终值是6
(float)(5) 最终值是 5.00000000
浮点数的存储所带来的问题
float和double都不能保证可以精确的储存一个小数
例子:
float i = 99.9;
printf("%f\n", i);
举例:
有一个浮点型变量X,如何判断x的值是否为零
if( |x-0.000001| <= 0.00001)
是零
else
不是零
为什么循环中更新的变量不能定义成浮点型
进制
1、什么叫进制
逢n进一
2、把r进制转成十进制
3、十进制转成r进制
4、不同进制所代表的数值之间的关系
十进制的3981转化化成 十六进制的F8D
是进制的3981和十六进制的F8D所代表的本质都是同一个
一些琐碎的运算符知识
自增或者自减
分类:
前自增 -- ++i
后自增 -- i++
前自增和后自增的异同:
相同:
最终都是使i的值加1
不同:
前自增整体表达式的值是i加1之后的值
后自增整体表达式的值是i+1之后的值
为什么会出现自增?
代码更精炼
自增的速度更快
*学习自增要明白的几个问题
1、我们编程时应该尽量屏蔽掉前自增和后自增的差别
2、自增表达式最好不要作为一个更大的表达式的一部分来使用或者说
i++和++i单独成一个语句, 不要把他作为一个完整复合语句的一部分来使用
如:
int m = i++ + ++i + i + i++;//这样不但是错的而且还是不可移植的
printf("%d %d %d", i++, ++i, i);//同上
break和contiune
break
break如果用于循环是用来终止循环的
break如果用于switch,则是用于终止switch
break不能直接用于if,除非if属于循环内部的一个子句
例子:
int i;
for (i=0; i<3; i++)
{
if (3 > 2)
break;//break虽然是内部的语句,但是break终止的是内部的循环;
printf("呵呵!\n");
}
return 0;
在多层循环中,break只能终止距离他最近的那个循环
例子:
for(i=1; i<3; ++i)
{
for(j=1; j<4; ++j)
break;
printf("共和最帅!");
}
continue
用于跳过本次循环余下的语句,
转去判断是否需要再次循环。
数组
为什么需要数组
为了解决大量同类型数据的存储和使用问题
为了模拟现实世界
数组的分类
一维数组
怎样定义一维数组:
为n个变量连续非陪储存空间
所有变量数据类型必须相同
所有变量所占的字节大小必须相等
例子:
int a[5];
有关一维数组的操作
初始化
完全初始化
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,3,4,5};//错误
只有在定义数组的同时才可以整体赋值,其他情况下整体赋值都是错误
int a[5] = {1,2,3,4,5};
a[5] = 100;//error 因为没有a[5]这个元素,最大只有a[4]
int a[5] = {1,2,3,4,5};
int b[5];
如果要把a数组中的值全部复制到b数组
错误的写法
b = a;//error
正确的写法
for(i=0; i<5; ++i)
b[i] = a[i];
二维数组
int a[3][4];
总共是12个元素,可以当作3行4列看待,这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]
a[i][j] 表示第i+1行 第j+1列的元素
int a[m][n]; 该二维数组右下角位置的元素只能是a[m-1][n-1]
初始化
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int a[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12};
};
操作 输出二维数组的内容
int main(void) {
int a[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int i, j;
//输出数组内容
for (i=0; i<3; ++i)
{
for (j=0; j<4; ++j)
printf("%-5d ", a[i][j]); //"-"表示左对齐,“5”表示占5个光标的位置
printf("\n");
}
return 0;
对二位数组排序
求每一行的最大值
判断矩形的相乘
多维数组
是否存在多为数组?
不存在!
因为内存是线性一维的
*n维数组可以当作每个元素是n-1维数组的一维数组
比如:
int a[3][4];
该数组是含有3个元素的一维数组
只不过每个元素都可以再分成4个小元素
int a[3][4][5];
该数组是含有3个元素的一个数组
只不过每个元素都是4行5列的二维数组
函数:
什么叫函数
逻辑上:能够完成特定功能的独立的代码块
物理上:
能够接受数据【当然也可以不接受数据】
能够对接受的数据进行处理
能够对数据处理的结果返回【当然也可以不反悔任何值】
总结:
函数是一个工具,他是为了解决大量类似问题而设计的
函数可以当做一个黑匣子
如何定义函数
函数的返回值 函数的名字(函数的形参列表)
{
函数的执行体
}
1、函数定义的本质是详细描述函数之所以能够实现某个特定功能的具体方法
2、return 表达式; 的含义:
1>终止被调函数,向主函数返回表达式的值
2>如果表达式为空,则只终止函数,不向主函数返回任何值
3>break是用来终止循环和switch的,return设计用来终止函数的
例子:
void f()
{
return;
}
int f()
{
return 10;//终止函数并返还10
}
3、函数返回值的类型也成为函数的类型, 因为如果 函数名前面的返回值类型 和函数执行体中的return 表达式;中的表达式类型不同的话,则最终函数的返回值类型 以函数名前面的返回值类型为准
例子:
int f()
{
return 10.5;//返回值为10,因为定义为int类型
}
函数的分类
有参函数 和 无参函数
有返回值函数 和 无返回值函数
库函数 和 用户自定义函数
值传递函数 和 地址传递函数
普通函数 和 主函数(main函数)
一个程序必须且只能有一个主函数
主函数可以调用普通函数 普通函数不能调用主函数
普通函数可以相互调用
主函数是程序的入口,也是程序的出口
注意的问题
函数调用和函数定义的顺序
如果函数调用写在了函数定义的前面,则必须加函数的前置声明
函数前置的申明:
1、告诉编译器即将可能出现的若干个字母代表的是一个函数
2、告诉编译器即将可能出现的入肝个字母所代表的函数的形参和返回值的具体情况
3、函数声明是一个语句,末尾必须加分号
4、对库函数的什么是通过#include<库函数所在的文件的:形参和实参
个数相同 位置一一对应 数据类型必须相互兼容
如何在软件开发中合理的设计函数来解决实际问题
一个函数的功能尽量独立,单一
多学习,多模仿牛人的代码
函数是C语言的基本单位,类是java,C#,C++的基本单位
double sqrt(double x);
求x的平方根
int abs(int x)
求x的绝对值
double fabs(double x)//求负的绝对值 比如 double fabs (-5.5)
求x的绝对值
变量的作用域的存储方式:
按作用域分:
全局变量
在所有函数外部定义的变量叫局部变量
全局变量使用范围: 从定义位置开始到整个程序结束
局部变量
在一个函数内部定义的一个变量或者函数的形参都称为局部变量
void f(int i)
{
int j = 20;
}
i和j都属于局部变量
局部变量的适用范围:只能在本函数内部使用
注意的问题:
全局变量和局部变量名冲突问题
在一个函数每部如果定义的局部变量的名字和全局变量的名一样时,局部变量会屏蔽掉全局变量
按变量的储存方式
静态变量
自动变量
寄存器变量
指针:
指针和指针变量的关系
指针的重要性
表示一个复杂的数据结构
快速的传递数据
使函数返回以上的值
能够访问硬件
能够方便的处理字符串
是理解面向对象语言中引用的基础
总结:指针是C语言的灵魂
指针的定义
地址
内存单元的编号
从零开始的非负整数
范围:4G 【0 -- 4G-1】
指针
指针就是地址,地址就是指针
地址就是内存单元的编码
指针变量是存放内存单元编号的变量,或者说指针变量就是存放地址的变量
指针和指针变量是两个不同的概念
但是要注意,通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样
指针的本质就是操作受限的非负整数
指针的分类
**1、基本类型指针
int * p;//p是变量的名字, int * 表示p变量存放的时int类型变量的地址
//int * p;不表示定义了一个名字叫做*p的变量
//int * p;应该这样理解:p是变量名,p变量的数据类型是 int *类型
// 所谓int *类型实际就是存放int变量地址的类型
int i = 3;
int j;
p = &i;//ok
/*
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的内容为地址的变量
*/
j = *p;//等价于j = i
printf("i = %d, j = %d\n", i, j);
附注:
* 的含义
1、乘法
2、定义指针变量
int * p;
//定义了一个名字叫p的变量,int *表示p只能存放int变量地址
3、指针运算符
该运算符放在已经定义好的指针变量的前面
如果p是一个已经定义好的指针变量
则*p便是 以p的内容为地址的变量
如何通过被调函数修改主调函数普通变量的值
1、实参必须为该变量的地址
2、形参必须为以该指针变量
3、在被调函数中通过
*形参名 = ....
方式就可以修改主调函数相关的数值
2、指针和数组
指针和一维数组
数组名
一维数组名是个指针常量
它存放的是一维数组第一个元素的地址
下标和指针的关系
如果p是指针变量,则
p[i]永远等价于*(p+i)
确定一个一位数组需要几个参数【如果一个参数需要处理一个一维数组,则需要接受该数组的那些信息】
需要两个参数:
数组第一个元素的地址
数组的长度
指针变量的运算
指针变量不能相加,不能相乘,也不能相除
如果两个指针变量指向的是同一块空间中的不同存储空间
则这两个指针变量才可以相减
一个指针变量到底占几个字节
预备知识:
sizeof(数据类型)
功能:返回值就该数据类型所占的字节数
例子:sizeof(int) = 4 sizeof(char) = 1
sizeof(double) = 8
假设p指向char类型(1个字节)
假设q指向int类型变量(4个字节)
假设r指向double类型变量(8个字节)
p q r 本身所占的字节数是否一样?
答案:p q r本身所占的字节数是一样的
总结:
一个指针变量,无论他指向的便改良占几个字节
给指针便改良本身只占四个字节
一个变量的地址使用该变量首字节的地址来表示
指针和二维数组
3、指针和函数
*4、指针和结构体
5、多级指针
专题:
动态内存分配
传统数组的缺点:
*1、数组长度必须事先制定,且只能是常整数,不能是变量
例子:
int a[5];//ok
int len = 5; int a[len];//error
*2、传统形式定义的数组,该数组的内存程序员无法手动释放
数组一旦定义,系统为该数组非陪的存储空间就会一直存在
除非数组所在的函数运行结束
在一个函数期间,系统为该函数中数组所分配的空间会一直存在
知道该函数运行完毕时,数组的空间才会被系统释放
*3、数组的长度一旦定义,其长度就不能在更改
数组的长度不能在函数运行的过程中动态的扩充或缩小
4、A函数定义的数组,在A函数运行期间可以被其他函数使用,
但A函数运行完毕之后,A函数中的数组将无法在被其他函数使用
传统方式定义的数组不能跨函数使用
为什么需要动态分配内存
动态数组很好的解决了传统数组的4个缺陷
传统数组也叫静态数组
静态内存和动态内存的比较
静态内存是由系统自动分配,由系统自动释放
静态内存是在栈中分配的
动态内存使用成需要手动分配,手动释放
动态内存实在堆分配的
跨函数使用内存的问题
结构体
为什么需要结构体
为了表示一些复杂的事务,而普通的基本类型无法满足实际要求
什么叫结构体
把一些基本类型数据组合在一起形成一个新的复合数据类型,这个叫做结构体
如何定义结构体
3种方式,推荐第一种
**//第一种方式
struct Student
{
int age;
float score;
char sex;
};
//第二种方式
struct Student2
{
int age;
float score;
char sex;
}st2;
//都三种方式
struct
{
int age;
float score;
char sex;
}st3;
怎样使用结构体变量
赋值和初始化
顶贴一的同时可以整体赋初值
如果定义完之后,则只能单个的赋初值
如何取出结构体变量中的每一个成员【重点】
1、结构体变量名.成员名
2、指针变量名->成员名
指针变量名->成员们 在计算机内部会转换成(*指针变量名).成员名 的方式来执行
所以说这两种方式是等价的
例子:
struct Student
{
int age;
float score;
char sex;
};
int main(void) {
struct Student st = {80, 66, 'F'};//初始值 定义的同时赋初值
struct Student * pst = &st;//&st不能够改成st
pst->age = 88;//第二种方式
st.age = 10;//第一种方式
return 0;
}
**1、pst->age 在计算机内部会被转换成(*pst).age,没有问什么这就是->的含义,这是一种硬性规定
**2、所以pst->age 等价于(*pst).age也等价于st.age
**3、我们之所以知道pst->age等价于st.age,是应为pst->age是转换成了(*pst).age来执行
**4、pst->age的含义:
pst所指向的那个结构体变量中的age这个成员
结构体变量的运算
结构体变量和结构体变量指针作为函数参数传递的问题
举例
动态构造存放学生信息的结构体数组