...

一些程序英语单词

void/ vɔɪd / n.空白        array / əˈreɪ / n.数组         string / strɪŋ / n.串

character n.字符         string n.字符串;线、细绳;

第一章 C语言预备知识

C++兼容大部分C语言的语法,而不兼容的语法,我们在日常的使用中,几乎是不会遇到的

使用C++源文件可以编译C语言代码

c语言关键字

printf函数

printf("占位1 占位2", 替换1, 替换2);

printf是一个变参函数,(参数个数和参数类型都是不确定的)

第一个参数必须是字符串,

第一个参数是需要输出的内容

第二级参数及后续参数将依次替换占位符

占位符的类型和数量需要和后续参数一一对应

输入输出缓存

输出函数有输出缓存,输入函数有输入缓存

我们往洗衣机里加衣服,往往时积攒一定数量后再扔洗衣桶里;缓存区相当于存放脏衣服的筒子,存放一定量的数据,在等待批量处理。

刷新缓存区:我们将1.数据发送到目的地,2.再清空缓存区,就是刷新缓存区。

输出缓存区

我们向控制台输出字符时,程序会先将打印的字符串,放到输出缓存区中,到特定时刻会一起显示到控制台上。

在计算机中需要将显示在屏幕上的数据发送到显卡,再由显卡进行显示。显然,累计一串字符再批量发送,比起一个一个发送更有效率。

输出缓存是系统行为,而不是函数特性

对于windows系统来说,数据一旦被写入到输出缓存区,就会立刻被刷新,然后显示

linix:数据中有换行符,立刻刷新缓存区,如果没有换行,直等到系统结束之后,才会刷新(行缓存:一行结束(换行符\n)之后必须刷新缓存)

#include <stdio.h>
#include <windows.h>
int main_h1()
{for (int i = 0; i < 10; i++){printf("Hello world %d", i);Sleep(500);}return 0;
}

输入缓存区

输入函数属于阻塞函数

当输入缓存区没有内容时,程序将阻塞在输入函数中,等待用户从键盘输入字符,并按回车确认。

只有当我们按下回车键,也就是输入换行符\n,输入的字符串将进入输入缓存区。

接下来,输入函数将1.从输入缓存区中获取字符,2.删除缓存区中得到的字符,并3.解除阻塞状态,继续执行代码。

只有当输入缓存区没有字符时,输入函数才会进入阻塞状态

getchar:输入一个字符

scanf:输入一行字符串

#include <stdio.h>
//输入缓存区
int main_h2(){//输入helloworldhelloworld//--变量声明char str[20];int i = 0;//--数据处理--//循环之后i的值刚好是所输入字符的后一个while (i <= 18){str[i++] =  getchar();if (str[i-1] == '\n') //看看在输入的字符是不是回车,是的话就跳出循环,不再输入字符。{break;}}str[i] = '\0'; //字符串结束的标志//--数据输出--printf( str);scanf("%s", str);printf(str);return 0;
}

getch函数,相当于无缓存的getchar

getche函数,会将输入的字符打印在控制台上,无需再次调用putchar打印

//不带缓存的输入函数
//getch函数,相当于无缓存的getchar
//getche函数,会将输入的字符打印在控制台上,无需再次调用putchar打印
int main_h3() {while (1){char c;c = _getche(); //无需回车,没有缓存区,getch立刻获取对应字符//  putchar(c);if (c == 'q'){break;}}return 0;
}
//总结练习
int main() {char c ;c = getchar();putchar(c);c = _getch();putchar(c);c = getchar();putchar(c);return 0;
}

第二章 计算

1 关于sacnf

scanf

读入整数:scanf("%d", a);

输出整数:printf("%d", a);

读入浮点数:scanf("%lf", c);

输出浮点数:printf("%f", c);

scanf_s

scanf_s括号内有三个参数,最后一个是变量a所占据空间的大小(单位为字节),可以用sizeof函数表示。

scanf_s("%c %c", &a, sizeof(a), &b, sizeof(b)); // 可不要写成 scanf_s("%c%c",&a,&b,1,1); 这样会出错的

scanf_s("%d %d", &a, &b, sizeof(a),sizeof(b)); // 如果输入整型数据,sizeof()在所有取地址的后面;而和上面一样输入字符的话,每个取地址后面都要有

如果输入的字符个数>=数组元素个数,是不会打印数组中的元素的。(而scanf函数就会打印出)

2 表达式

运算符表示动作;

算子参与运算的东西;

四则运算

c语言:加 减 乘 除 括号 取余(取余数,如9%2=1)

计算时间差程序

3 运算符优先级

结合关系:单目和赋值运算符 自右向左 运算

单目:就是给一个数取相反数或者不变(如a*-b 就是a乘以b的相反数)

4 复合赋值

5个算数运算符(+ - * / %)可以和唯一一个赋值运算符结合起来,形成5个复合赋值运算符(+= 、-+ 、*= 、/=、%=)

算术运算符+赋值运算符=复合赋值运算符(注意不要有空格,如a += 5,就是a=a+5)

递增递减运算符:++ --,作用是加一或减一

a++ 就是 a = a+1 (a += 1)

  • 前缀与后缀,都是给a加了1
  • a++ 是以前的那个值
  • a-- 是以后的那个值

对于表达式,后缀是以前的值,前缀是以后的值

数据类型


第三章 判断与循环

循环条件必须以关系运算符来表示,满足则继续循环;不满足则结束循环。

for(1; 判断; 3);

do{}while(判断)

while(判断)

1 关系运算符

== 相等

!= 不相等

>大于

>= 大于或等于

< 小于

<= 小于或等于

关系运算的结果

成立 为 1

不成立为 0

优先级

赋值运算<关系运算<算数运算

如int r = a>0 把a>0的真假01值代入r中

连续的运算从左到右进行

2 否则的话,else

if( the condition ){ your function }

else{ your condition }

如果成立的话,运行后面

如果不成立的话,另一个运行

写代码有的时候是你能不能让更多的人读懂你的代码

3 判断与循环

if判断

if ( the formula ) //如果小括号里面的关系运算为1,则继续

{ } //如果为零,则跳过花括号继续运行


对所有循环:条件成立,继续循环;条件不成立,离开循环

while循环

1.一个数字有几位数?

2.数位数的算法

while循环:当条件满足时,就一直做这个循环

一定要有走出循环的条件

总结:if和while竟然只有一次和多次的区别

if是判断是不是,然后就走了

while除了要判断是不是,是的话增加条件继续循环,直到不是就走了(反之同理)

列出变量表格法是人脑模拟计算机的一种有效手段,特别是对于循环程序来说。

在程序中加上“//”使得这行程序失效

对于编程中的除法运算,对于/10,就是划掉最后一位

2.printf here或者printf 数值 可以起到标记的作用

dowhile循环

dowhile格式

do

{

} while()

dowhile循环和while循环的唯一区别就是while先判断条件,dowhile后判断条件

左边是dowhile循环, 右边是while循环

for循环

结构

for(初始条件;循环条件;每轮循环结束完要做的动作) {}

算法:阶乘

for 等于 对于

for(count=1;count>0;count--)

对于初始化的count=1,判断count是否大于0,如果是就执行循环体,执行完循环体一次后,再让count减1;重新判断count是否大于0,就这样循环,直到count<=0就退出循环

for 循环的循环次数

for(i=0;i<n;i++) 表示 1.循环n次 2.初始值i=0,循环结束以后i=n

for(i=1;i<=n;i++) 表示 1.循环n次 2.初始值i=1,循环结束以后i=n+1

for 与 while 的异同:for和while是等价的,总可以找到办法可以互换

//三种循环for、 while、 dowhile

//三种循环的使用条件

如果有固定次数 用for

如果必须执行一次 用do while

其他用 while


第四周 进一步的判断与循环

一、逻辑运算

  • 逻辑运算是对逻辑量进行的运算。结果只有0或1
  • 逻辑量是关系运算或逻辑运算的结果

! 逻辑 非 !a a是对的,结果就是错。a是错的结果就是对。

&& 逻辑 与 a&&b a,b都对结果是对,否则都是错

|| 逻辑 或 a||b 一个对,结果就是对

如何表达x在(4,6)?

错误示范:4<x<6的结果是一个逻辑值(0或1)

正确示范:4<x && x<6

!a<20 意思是 先计算“!”运算符,a如果为0,!a为1;a如果不为0,!a为0

优先级

短路

条件运算

三个例子

如第一个,如果m小于n的话,那么结果是x,否则是a+5

最好不用嵌套的条件表达式

逗号运算符

只在if中使用

//嵌套的if-else

  • 如果没有{}的话,else和最近的if匹配

建议

  1. if和else后面接{},即使只有一句话

代码:求三个数中最大的那个数

嵌套的if-else语句

//2级联的if else-if

分段函数的代码

这是一个递进的关系

单一出口:只有一个printf比较好

//多路分支 switch-case

惯例

switch(){}

switch(控制表达式只能是整数)

{

case 常量:

语句

...

case 常量:

语句

...

case 常量:

语句

...

default:

语句

...

}

case 1,2,3..像是一个位置点,一个路牌,不能阻止语句往后走,直到遇到一个break

只有当碰到break的时候离开

代码:成绩转换

利用除以十划去最后一位的性质

//循环计算

log2x的代码

小套路int t=x 最后输出t,就可以防止最后输出x

x>1等价于x>=2

第二个例子

模拟比较少的次数,做推断

如何判断一次循环完成? 可以在其中输出一个数,最终输出了几个数,就完成了几次循环

4.4.2 算正整数的平均数代码

4.4.3猜数代码

随机数每次召唤rand()就得到一个随机的整数

小套路:x%n的结果是[0,n-1]之间的一个整数,特别地,x%100的结果在[0,99]之间,相当于划掉除了最后两位的前面所有数,只保留最后两位数

另外,用二分法最多7次就能求出来结果

4.4.5 求一个整数的逆序

  • %10 得到个位数
  • /10 划去个位数
  • *10 往左移动一位

4.5 判断和循环常见的错误

永远在if和else后面加{}

一定不能在if()后加上;这样的意思是if语句在这里就结束了,后面的大括号也就是正常运行

初学者一开始就尊重warning

if和else后面加大括号{}

大括号内的内容增加一个缩进

第五章 循环控制

1 循环控制

素数:只能被1和自己整除 不包括1 5=1*5

如何判断一个自然数是不是素数,就看它能不能被1和它本身之间的数整除

break:跳出for{循环体},打破循环,出去

continue:跳过for循环在这一轮中剩下的语句,进入下一轮的for循环;剩下的不做了,回去做新一轮循环;

判断一个数是不是素数

2 多重循环

如何用1角、2角、5角的硬币凑出10元一下的金额呢?

break和continue只能对它所在的那层循环做

3 循环应用

求最大公约数

正序分解正整数

第六章 函数

1 初见函数

以下两个例子来说明函数的重要性与必要性!

素数求和

求和

2 函数的定义和调用

下图是函数的定义格式:

调用函数

格式:

函数名称 (参数列表);

参数列表中的参数会被按照顺序依次用来初始化函数中的参数

4 从函数中返回值

如果函数需要返回一个结果,那么需要用return把那个结果交给调用它的地方

return语句做两件事

1.停止函数执行

2.送回一个值

return两种写法

1.return;

2.return 表达式;

一个函数可以出现多个return,但是这样写错之后修改会很麻烦。

没有返回值的函数:void 函数名,可以没有return

如果函数有返回值,则必须使用带值得return

5 函数声明(也叫函数原型)

格式:

数据类型 函数名称(数据类型 数据名称, 数据类型 数据名称); //函数声明 也叫函数原型 (参数列表里的是形式参数)也可以不写形参,只写数据类型

...

数据类型 函数名称(数据类型 数据名称, 数据类型 数据名称) //函数定义

{

函数体

}

函数告诉编译器这个函数的三要素:

  • 数据类型 或者叫返回类型
  • 函数名称
  • 参数列表

函数定义

三要素+函数体

末尾添加

函数调用

数据类型 函数名称(参数列表); 三要素

主体函数中调用

函数声明

函数名称(参数列表);

页面最上面

6 参数传递

如果函数有参数,调用函数时必须传递给它数量、类型正确的值

可以传递给函数的值是表达式的结果,这包括:

字面量、变量、函数的返回值、计算的结果

类型不匹配的问题:

调用函数时给的值与参数类型不匹配时,编译器会悄悄给你把类型转换好,这是C语言最大的漏洞,而后续的Java/C++在这方面很严格

传值

每个函数有自己的变量空间,参数也位于这个独立的空间中,和其他函数没有关系

7 本地变量 (p74)

本地变量又称局部变量,自动变量。

定义:函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称作本地变量;

定义在函数内部的变量就是本地变量;

参数也是本地变量。

变量的生存期和作用域:

生存期:从变量出现开始,到它消亡;(实际为占据和释放内存);

作用域:在(代码内)什么范围内可以访问这个变量;

本地变量的生存期和作用域都在块内即大括号{ };

注:{ }所包含的代码区域,就称为块

但是if( )语句括号里的i,在它后面的{ }里依然有效,可以把()里的看作整体的一个子块。

本地变量的规则:

(1)本地变量就是定义在块内的;

(2)它可以定义在函数的块内;

(3)也可以定义在语句的块内;

(4)甚至可以随便拉一对大括号来定义;

(5)程序运行进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了;

(6)块外面定义的变量在里面仍有效;

(7)块里面定义了和外面同名的变量回掩盖外面的;

(8)不能再一个块内定义同名的变量;

(9)本地变量不会被默认初始化;

(10)参数在进入函数之前时就被初始化了;

————————————————

原文链接:区分本地变量、全局变量以及静态本地变量_Star_jiang的博客-CSDN博客_什么是本地变量

8 其他细节

  1. C语言不允许其他函数嵌套定义
  1. int main() 也是一个函数
  2. 可以写成 int main( void )
  3. return 0 确实有人看
  4. windows: if error level 1

9 标识符作用域

标识符作用域

被花括号包括的代码,形成了代码块

int n = 3;

编译器预留sizeof(int)字节的内存空间

标识符n指代上述的内存空间

标识符n为int类型,用于规范所指代内存空间中数据的使用

使用标识符n指代内存空间,有一定的指代范围:从标识符n开始,到块的结束。

两个代码块平级时,可以声明两个相同的标识符

#include <stdio.h>
int main()
{{int n;printf("&n=%#p\n", &n);}{float n; //如果把这一行去掉,则编译器会报错,因为编译器不知道n指代的是什么printf("&n=%#p\n", &n);}return 0;
}
#include <stdio.h>
int main()
{   //代码块A{int n; //标识符n的上届printf("&n=%#p\n", &n);//代码块B,形成上下级代码块{float n; printf("&n=%#p\n", &n); //内层作用域覆盖外层作用域,即如果内层声明了和外层一样的标识符,那么内层里的标识符只在内层起作用}//标识符n的下届}return 0;
}

带括号的块作用域

函数列表声明的标识符。作用域为整个函数的花括号内。除了函数,if、while、for也可以形成带括号的块作用

文件作用域

如果不在花括号内声明,在花括号外生命,则作用域从声明开始,到源文件结束,即是文件作用域。

同样,此时在花括号内声明了和花括号外一样的标识符,则形成上下级关系,下级覆盖上级。

第七章 数组

1 引例 输出大于平均数

如何写一个程序计算用户输入数字的平均数,并输出所有大于平均数的数

作用:记录很多数

因此能够起到一个数字标记作用。而此处将输入的x的值赋给number[cnt],既可以理解为将x标记数字,也可以理解为把数字所在的数组位置填入一个要素

这里的意思是,从0开始的cnt本身会随着循环的累计而一个数一个数增大

2 定义数组

语法

<类型> 变量名称[元素数量]={填入数字,用逗号隔开};

例如:int number[3]={1,2,3};

注意:元素数量必须是整数

数组是一种容器,特点是:

1.其中所有元素具有相同的数据类型

2.一旦创建,不能改变大小

3.元素在容器中连续依次存在

下标从0开始

有效的下标范围:[0,数组的大小-1]

编译器和运行环境都不会检查是否越界

长度为零的数组:int a[10]。可以存在,但无用

3 用数组做散列计算

统计次数

输入数量不确定的[0,9]范围内的整数 ,统计每一种数字出现的次数,输入 -1表示结束

int a [3][5]

三行五列

列数不可以省略

每行一个{} 用,分割

4 二维数组

二维数组的遍历

以a[3][5]为例:

for( i=0; i<3 ; i++)

{

for( j=0; j<5 ; j++){}

}

a[i][j]是一个int(整数),表示第i行第j列上的单元

a[i,j]是什么?

二维数组初始化

int a[][5] = {

{0,1,2,3,4,}

{2,3,4,5,6,}}

列数是必须给出的,行数可以由编译器来数

每一行一个{},逗号分隔

最后的逗号可以存在,有古老的传统

如果省略,表示补零

也可以用定位

井字棋游戏

5 数组的运算

1.搜索

如何在一组数据中找到某个数?

注意:数组作为参数并要表达数组大小时,往往必须用另一个参数来表示数组大小;

不能在[]中给出数组大小;

不能利用sizeof来表示数组大小。

数组的集成初始化

  1. int num[] = {1,2,3,4,5,6};

不用在中括号里写出数组的内单元的数量,只用在后面中括号填数字就可以了。

  1. int a[6]={2}; // 2 0 0 0 0 0

声明了数组元素数量之后,如果花括号内元素数量比a少,那么后面全补零

集成初始化时的定位

int [13] = { [1]=2 , 4 ,[5]=6}; //2 4 0 0 6 0 ...

[n]表示位置;不是n的位置都为0;

如何表示数组中元素的数量

int a[3] = {1,3,9};
printf("d",sizeof (a)/sizeof (a[0])) //3

sizeof给出的是所占内容的大小,单位是字节

sizeof (a)/sizeof (a[0]); //永远指的是数组的大小,得到的永远是数组内的单

好处:一旦修改初始数据,不用修改遍历的代码

数组的赋值

数组不能赋值给数组

只能通过遍历把一个数组赋值给另一个数组

for ( i=0 ; i<length ; i++ ){b[i] = a[i]; }

遍历数组

遍历输出、遍历初始化、遍历赋值、遍历搜索、遍历条件输出

for ( i = 0 ; i < length ; i++ ){     // (0; 数组长度; ++)printf(" %d : %d " , i , a[i]);
for ( i = 0 ; i<length ; i++ ){a[i] = 0; }
for ( i = 0 ; i < length ; i++ ){        a[i] = b[i]; }
for ( i = 0 ; i < length ; i++){if( a[i] == key ){ret = i;printf("%d", a[i]); break;}
for ( i = 0 ; i < lengh ; i++ ){if( a[i] > arverage ){printf( " %d " , a[i]);
  • 通常都是使用for循环,从0开始到数组的长度,条件是下标小于数组长度,这样循环体内最大的i刚好是数组最大有效下标。
  • 倒序:从数组下标最大开始,条件为下标大于等于0,i--
  • 以下一类常见的错误:
    • 循环结束的条件是<=数组长度!
    • 离开循环后,继续使用i作为下标!

这类错误都是离开循环之后,下标刚好是length,也即无效的下标。

2.素数

第八章 指针

一个变量名就代表了一个空间、一个盒子

指针名=地址=盒子=钥匙

盒子里面放的是钥匙,这把钥匙指向了内存空间的某个位置

门牌号就是地址,地址就是门牌号

变量名就是地址,地址就是变量名

指针就是钥匙,钥匙就是指针

指针的引入

指针开门

指针要回答两个问题,一个是用多大的空间存储地址,一个是这个地址怎么访问空间

-访问方式:*变量名;

当编译器从右往左看到一个不认识的单词,再往左看到星号,就认定是一把钥匙,即指针

案例char *abc;

abc和char没有什么关系,本质是星号。

看一个变脸是不是指针,主要看左边是不是*,不要被前面的数据类型给欺骗了

指针变量的大小完全由编译器决定,X86是4,X64是8;

看到a[]的形式就把a看成地址,通过这个地址找里面的东西,当成地址去偏移。

数组仅仅是申请空间的一种手段,本质是通过一个固定的单位来访问操作空间

#include <stdio.h>
int main() {// 字符数组-->空间,char buf[]={0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40};char *add1 = &buf[3]; //*是把一个变量升级为地址,前面的数据类型告诉编译器操作方式,一个一个字节操作int *add2 = &buf[3];printf("the add1:%p, the add2:%p\n", add1,add2); //地址printf("the value1: %x %x\n", add1[0], add1[1]); //33,34printf("the value2: %x %x\n", add2[0], add2[1]); //从高位到地位 36353433, 40393837}

前言

1.指针就是地址,地址就是指针。

2.经常所提到的“指针”二字,有两种意思,一种意思是代表指针变量(是一种变量,用来存储地址),一种就是地址。要跟据具体语境进行分析。

3.指针可以指向变量,也可以指向给定特定内存地址的指针

4.指针可以指向基本数据类型、数组

& 引用运算符或地址运算符

&是 scanf("%d", &i); 里的&,读做英文单词"and"

&的作用:获取变量的地址,它的操作对象必须是变量

地址的大小是否与int相同取决于编译器。

int i;
printf("%p", &i);

打印输出i的地址:printf("%p", &i);&i 可读作 i的地址

打印指针老老实实用%p。

“%p”中的p是pointer(指针)的缩写。

%p是打印地址(指针地址)的,是十六进制的形式,但是会全部打完,即有多少位打印多少位。

%x/X:无符号十六进制整数(字母小写,不补零)

%x、%X和%p的相同点都是16进制,不同点是%p按编译器位数长短(32位/64位)输出地址,不够的补零

  • &不能对没有地址的东西取地址,取地址必须有明确的变量。

* 解引用运算符

*有两个作用

1.访问地址上的变量

2.定义一个指针变量

指针定义:

所谓指针变量,就是记录保存地址的变量,就是*P中的P!

声明指针的格式:

数据类型 星号* 变量名称 = &某个变量; //指向某个数据类型的指针

如:int * p; //p是一个指向int类型的指针变量,*p是int类型*p可读作p所指向的值

*p = &i;

类型说明符表明了指针所指向对象的类型,星号(*)表明声明的变量是一个指针。

int *p = &i; p指向i,p里面的值就是变量i的地址。

int *p,q; *p是一个int,表示p是一个指针,指向int;q是一个普通的数字。

int* p与int *p没有区别。

变量的值是内存的地址

普通变量的值是实际的值

指针变量的值是具有实际值的变量的地址

作用:访问指针所表示的地址上的变量,可以粗糙地把*p看作成一个普通变量

可以做右值,也可以做左值

为什么int i;scanf("%d", i);编译没有报错?

指向指针的指针

#include<stdio.h>
int main()
{int x = 5;int* p = &x; // p是指向int类型的指针变量,存放x的地址*p = 6; // 访问p所存放的地址int** q = &p; // q存放指针p的地址int*** r = &q; // r存放指针q的地址printf("x = %d            x的值6\n", x); // x的值6printf("p = %d       x的地址\n", p); // x的地址,  指针变量p存放x的地址 <==> p指向x <==> *p==xprintf("*p = %d             x的值6\n", *p); // x的值6。p存放x的地址 <==> p指向x <==> *p == x。*p就是访问x的地址上的数值,x的地址上的数值就是x的数值,x的数值为6printf("q = %d      p的地址\n", q); //p的地址printf("*q = %d       x的地址\n", *q); // x的地址:q存放p的地址 <==> q指向p <==> *q == p。*q就是访问p的地址上的数值,p的地址上的数值是p的数值,p的数值是x的地址printf("**q = %d           x的值6\n", **q); //x的值6printf("r = %d      q的地址\n", r); // q的地址printf("*r = %d      p的地址\n", *r); // p的地址printf("**r = %d    x的地址\n", **r); // x的地址printf("***r = %d      x的值6\n", ***r); // x的值6
}

特定类型的指针变量来存放特定类型变量的地址

位bit<字节byte<KB<MB<GB

1 byte = 8 bit

每个字节对应一个数字地址

指针实例

  • 突然的感悟:只要定义了a,那么*a和&a就可以随便用。
  • &和*,一个是取变量的地址,一个是取地址的变量。

指针应用一:交换两个变量的值

#include <stdio.h>int swap(int* a, int* b); //函数声明int main() {int x = 1;int y = 2;swap(&x, &y);   //把地址发送给下面swap函数printf("x=%d\ny=%d", x, y);return 0;
}int swap(int *a, int *b) {   // 函数定义int temp;temp = *a; //temp 获得 a所指向对象的值*a = *b; // 把b所指对象的值交给a所指对象的值*b = temp; return 0;
}

指针应用二:找到一个数组中的最大值和最小值并输出

#include <stdio.h>void minmax(int num[], int len, int* min, int* max);int main() {int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55};int min, max;int len = sizeof(a) / sizeof(a[0]);minmax(a, len, &min, &max); // 把a、min、max的地址发送给下面minmax函数printf("min=%d\nmax=%d", min, max);return 0;
}
/*
三要素:
返回类型 void
函数名称 minmax
参数列表 数组 数组长度 最大值容器 最小值容器
*/
void minmax(int num[], int len, int *min, int *max) {*min = *max = num[0];for (int i = 0; i < len; i++) {if (num[i] < *min) {*min = num[i];}if (num[i] > *max) {*max = num[i];}}
}

指针应用二b:如果除法成功,返回1,否则返回0

让函数返回特殊的不属于有效范围内的值来表示出错:-1或0。

但是当任何数值都是有效的可能结果时,就得分开返回了。

/*当除数不为零时,即算作除法成功*/
/*如果可以成功,则输出结果*/
#include <stdio.h>int divide(int x, int y, int* result); //函数声明int main() {int a = 5;int b = 0;int c;if ( divide(a, b, &c) ) {    //因为a,b发送到下面函数之后,直接赋值给了*result,从而不需要返回到main主体函数,所以不需要&。//把返回值作为真假,很好的例子printf("%d/%d=%d", a, b, c);}else {printf("除法不成功");}return 0;
}
/*
三要素:
返回类型 int
函数名称 divide(除号键)
参数列表 被除数 除数 结果
*/
int divide(int x, int y, int *result){int ret = 1;if (y == 0) {ret = 0;}else {*result = x / y;}return ret;
}

指针实例4

指针与数组的关系

数组本质上就是指针

数组名可以看作是一个指针常量

int A[10];

相同的值

*(A + i) 与 A[i] 完全一样

*(A + 1) = A[1];

*(A + 2) = A[2];

A[3] = *(A + 3);

相同的地址

(A + i) 与 &A[i] 完全一样

A = &A[0]; // 相同的地址,数组名是数组首个元素的地址

A + 1 = &A[1]; // 相同的地址,数组名+1则是数组第二个元素的地址

以下四种函数原型是等价的(只在参数列表中等价):

int sum(int *ar, int n);

int sum(int ar[], int n);

int sum(int *, int);

int sum(int [], int);

数组变量是特殊的指针:

数组名是数组首元素的地址,也就是说,如果A是一个数组,下面语句成立:

A == &A[0]; //数组单元表示的是变量,需要用&

int* P = A; //A本身就是地址,所以无需用&符号

"[ ]" 运算符可以对数组做,也可以对指针做:

p[0] = a[0]

"*" 运算符可以对指针做,也可以对数组做

*A = A [0];//也就是*(A + 0) = A[0]

*(A + 1) = A[1];*(A + 2) = A[2];A[3] = *(A + 3;) // 相同的值

数组变量是const的指针,所以不能被赋值

int a[] <==> int * const a=...

#include<stdio.h>
int main()
{int A[] = { 2,4,5,7,8 };int* p = A;p=p+1; //printf("%d\n", A); //首元素地址printf("%d\n", p); //此时p中存放的地址就是数组A首元素地址+1,二者已经不相等了for(int i = 0; i<5; i++){printf("%d\n", &A[i]); // 每个元素的地址printf("%d\n", A+i); // 每个元素的地址printf("%d\n", A[i]); //每个元素的数值printf("%d\n", *(A + i)); //每个元素的数值}
}

指针和二维数组

//二维数组由多个一维数组组成

//二维数组的数组名,相当于一个二级指针,用两个 * 才能解引用出来值。

//一个 * 解引用出来的值还是地址,但这时候的地址是一维数组的地址

二维数组是一维数组的数组,即指针的指针,故进行一次解引用,得到的还是地址。

指向一维数组的指针的声明格式

数据类型 (*指针变量名称)[一维数组中元素数量]

int (*p)[3] = B; //会返回一个指向含有3个整型元素的一维数组

对于二维数组来说

B[i][j] = *(B[i]+j) = *(*(B+i)+j)

//指针与二维数组
//二维数组由多个一维数组组成
//二维数组的数组名,相当于一个二级指针,用两个 * 才能解引用出来值。
//一个 * 解引用出来的值还是地址,但这时候的地址是一维数组的地址
#include <stdio.h>
int main()
{int B[2][3] = {{2,3,6},{4,5,8}};int(*p)[3] = B; //声明一个指向一维数组的指针,而不是一个整型的指针,指针的类型是很重要的,尤其是在你解引用、指针运算的时候printf("%d %d %d\n",    p,      B,      &B[0]); //相当于一个二级指针,printf("%d %d %d\n",   p+1,   B+1,   &B[1]);printf("%d %d %d\n",   *(B+0),        B[0],   &B[0][0]);printf("%d %d %d\n",    *(B+1),        B[1],   &B[1][0]);printf("%d %d %d\n",    *(B+1)+2, B[1]+2, &B[1][2]);printf("%d\n", *(*B + 1));printf("%d\n", **B);printf("\n");printf("\n");printf("\n");int A[] = { 1,2,3 };//int* P = A;printf("%d %d\n", A, &A[0]);printf("%d %d\n", A+1, &A[1]);printf("%d %d\n", *A, A[0]);printf("%d %d\n", *(A+1), A[1]);printf("%d %d\n", *(A+1)+1, A[1]+1);return 0;
}

数组作为函数参数

#include<stdio.h>
int SumOfElements(int A[], int size) // int A[] 与 int* A 相同
{int i, sum = 0;for (i = 0; i < size; i++){sum = sum + A[i];}return sum;
}int main()
{int A[] = { 1,2,3,4,5 };int size = sizeof(A) / sizeof(A[0]); //数组的元素数量int total = SumOfElements(A, size);printf("数组每个元素的和为%d\n", total);
}
#include<stdio.h>
int Double(int* A, int size)
{int i;for (i = 0; i < size; i++){*(A+i) = 2 * *(A+i);}return 0;
}int main()
{int A[] = { 1,2,3,4,5 };int size = sizeof(A) / sizeof(A[0]); //数组的元素数量Double(A, size);int i;for (i = 0; i < size; i++){printf("%d\n", A[i]); }}

指针运算

sizeof(char) = 1;

sizeof(int) = 4;

sizeof(short) = 2;

sizeof(long) = 4;

sizeof(double) = 8;

sizeof(float) = 4;

指针减法

两个地址相减得到的是地址的差/类型单元字节

*p++

指针比较

><==,比较的是地址

0地址

当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址

所以你的指针不应该具有0值

因此可以用0地址来表示特殊的事情:

返回的指针是无效的

指针没有被真正初始化(先初始化为0)

null是一个预定定义的符号,表示0地址

有的编译器不愿意你用0来表示0地址

指针的类型

无论指向什么类型,所有的指针的大小都是一样的,因为都是地址

但是指向不同类型的指针是不能直接相互赋值的

这是为了避免用错指针

指针的类型转换

void*表示不知道指向什么东西的指针

计算时与char*相同(但不相通)

指针也可以转换类型

int*p=&i;

void*q=(void*)p;

这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量

我不再当你是int啦,我认为你就是个void

总结指针的作用

需要传入较大的数据时用作参数

传入数组后对数组操作

函数返回不止一个结果

需要用函数来修改不止一个变量

指针和动态内存

内存

动态内存管理

我们可以将计算机内存视为字节数组,每个字节都有一个唯一的地址,这就是为什么我们称计算机内存是字节可寻址的。换句话说,可以寻址的最小数据对象是一个字节。

指针的值仅仅是计算机中存储某些变量的内存地址,如果变量占用了内存中的多个字节,则此变量的地址定义为,它所占用的所有字节的最低地址

指针的不同类型表示该指针可以保存相应类型的变量

int* p1; //可以保存整形变量的地址

float* p2; //可以保存浮点数的地址

char* p2; //可以保存char类型变量的地址

字节的换算

1、字节(Byte) = 8位(bit)

  2、1KB( Kilobyte,千字节) = 1024B

  3、1MB( Megabyte,兆字节) = 1024KB

  4、1GB( Gigabyte,吉字节,千兆) = 1024MB

  5、1TB( Trillionbyte,万亿字节,太字节) = 1024GB

地址与字节一一对应

为什么硬件所能访问的最小单位是字节,而不是位呢?
因为硬件是通过地址总线访问内存的,而地址是以字节为单位分配的,所以地址总线只能精确到字节。那如何控制到字节的某一位,就要通过“位运算符”,即通过软件的方式来控制。

如果成功申请到了地址,系统会保存首地址和空间大小

堆-栈

内存有四个部分:code代码段、static静态/global全局数据段、stack栈区、heap

code作用:存放指令

static:存放全局变量,也即不在函数中声明的变量,生命周期贯穿整个应用程序,在程序运行期间都可以访问它们

栈stack:存放所有局部变量函数调用信息。局部变量是在函数内声明的,只在函数执行期间存活。发生函数调用的时候,会从栈上分配一些内存,称为那个函数的栈帧。任何时候正在执行的函数是栈顶的那个函数

以上三个区段在运行期间大小不会增长

程序开始执行的时候,操作系统分配了一些内存,假设OS分配了1MB的内存用来作为栈,但实际的栈分配是在运行时,如果我们的栈增长超出了预留内存的大小,这被称为栈溢出

栈溢出:我们耗尽了栈空间

heap:堆,又叫空闲池。它的大小在整个程序运行周期都是可变的,也没有特定的规则来分配和销毁相应的内存。程序员可以完全控制在堆上分配多少内存,数据保留到什么时候,你几乎可以使用堆上的任意内存 从程序员的角度来看,仅仅是一块可以自由使用的内存。我们可以根据内存灵活的使用。堆也被称为动态内存,使用堆意味着动态内存分配。

为了在c中使用动态内存,我们需要知道四个函数

malloccallocreallocfree

C++涉及newdelete

malloc、realloc、calloc、free函数

malloc函数

void* malloc(size_t size);

如果申请成功,返回值为成功申请的内存的首地址

如果申请失败,返回值为NULL

一、格式:malloc(单元的数量*每个单元的字节数);

二、概念:malloc函数其实就是在上找一片指定大小的空间,为你预留空间,然后将这个空间的首地址给一个指针变量,就可以通过解引用这个指针变量来进行操作。malloc函数并

不关心你要往内存中存储字符型或整型还是什么型,他只是简单地返回一个指向起始地址地void类型的指针。

三、使用:

malloc函数的括号里应该用数量*sizeof(类型),而不要习惯去使用数量*4或1,因为变量的大小取决于编译器。为了能够使用这块内存,我们首先需要进行指针类型转换

(int*)malloc(3*sizeof(int));// 可以往地址上赋值

*p = 2;也可以写成p[0]

*(p+1) = 2;也可以写成p[1]

*(p+2) = 2;也可以写成p[2]

向macllon申请的空间大小是以字节为单位的

动态分配的内存,不需要像全局变量一样在程序的整个生命周期内一直存活,我们可以控制什么时候去释放堆上的内存。

任何时候我们使用malloc分配的内存,最终都要通过调用free进行释放。

free,我们把内存的起始地址传入free,这样一来,内存会被释放。

四、申请失败,当堆上没空间的时候

如果malloc找不到空闲的内存块,申请失败,也就是说不能在堆上成功分配内存,他会返回NULL

#include <stdio.h>
#include <stdlib.h>int main()
{int  a; // 声明一个局部变量,在栈上分配int* p; // 局部整型指针变量,也在栈上分配/*如果说想要获得一些堆上的空间,我们需要调用malloc函数,需要在参数表中表示在堆上分配多少字节的内存*/p = (int*)malloc(sizeof(int)); // malloc返回void类型指针,而p是int型指针,所以要做一个强制转换*p = 10;/*任何我们使用malloc分配的内存,最终通过调用free进行释放*/free(p); // free,我们把内存的起始地址传入free,这样一来,第一块内存会首先被释放,然后指向另一块内存//如果我们想要在堆上分配一个数组,比如在堆上存储一个整型的数组,我们只要调用malloc,传入总的数组大小字节数,(20*整型的大小)p = (int*)malloc(20*sizeof(int)); // malloc函数又为我们找了一块内存,让p指向这个内存*p = 20; //malloc函数不会被自动回收,如果不释放就会浪费内存,而内存是重要的资源free(p);return 0;
}
/*用malloc函数,倒序输出一个数组*/
#include <stdio.h>
#include <stdlib.h>
int main() {int number;int* a;int i; //在之前的版本,定义一个变量要在最前面printf("输入数量\n");scanf_s("%d", &number);a = (int*)malloc(number * sizeof(int)); //数量*sizeof(数据类型)for (i = 0; i < number; i++) {scanf_s("%d", &a[i]);} // 遍历输入for (i = number - 1; i>=0; i--) { //循环中,满足条件循环继续,不满足则跳出循环printf("%d", a[i]);} //倒序输出free(a); //malloc函数使用后一定要释放return 0;
}
#include <stdio.h>
#include <stdlib.h>int main()
{int  a; // goes on stack 一个局部变量,在栈上分配int* p; // 局部整型指针变量,也在栈上分配//如果说想要获得一些堆上的空间,我们需要调用malloc函数//需要在堆上分配多少字节的内存p = (int*)malloc(sizeof(int)); // malloc返回void类型指针,而p是int型指针,所以要做一个强制转换*p = 10;// 任何我们使用malloc分配的内存,最终通过调用free进行释放free(p); // free,我们把内存的起始地址传入free,这样一来,第一块内存会首先被释放,然后指向另一块内存p = (int*)malloc(sizeof(int)); // malloc函数又为我们找了一块内存,让p指向这个内存*p = 20; //malloc函数不会被自动回收,如果不释放就会浪费内存,而内存是重要的资源free(p);/*我们想在堆上分配一个数组,比如说有20个元素的整型数组,就用malloc函数传入数组大小字节数(20*整型的大小)*/p = (int*)malloc(sizeof(20*int)); //整型指针变量p内存储的是大小为80个字节的基地址return 0;
}
/*用malloc函数给指针变量P分配100mb的空间,然后输出它的空间*/#include <stdio.h>
#include <stdlib.h>
int main(void) {int* p;int cnt = 0;while (p = malloc(100 * 1024 * 1024)); {cnt++;}printf("分配了%d00MB的空间\n", cnt);return 0;
}

五、总结

malloc - void* malloc(size_t size)

参数是内存里字节数大小,注意到这个数据类型是size_t,他是一个正整数,可以把他想像成unsigned int,大小不能是负数,可以是0或者一个整数。

malloc返回一个void指针,前面我们已经讨论过了void指针,这个指针指向了(存储了) 分配给我们的内存块的第一个字节的地址

所以,malloc函数好像在说:“嘿,请给我分配这么多个字节的一块内存。”

alloc函数

alloc - void* malloc(size_t num, size_t size)

alloc和malloc有点像,都是返回void指针,但是calloc返回两个参数:(特定类型的元素的数量, 类型的大小)

int* p = (int*)alloc(3,sizeof(int))

alloc和malloc还有一个区别:malloc分配完内存后并不会对其进行初始化,如果你没有填入值的话,你将会得到一些随机值(垃圾)

但是如果使用calloc的话,会对其初始化为0。

#include <stdio.h>
#include <stdlib.h>
/*输入一个数字,输出从一到这个数字的所有数,要求用到数组*/
int main()
{int n;printf("请输入一个数字\n");scanf_s("%d", &n);//int A[n]; // 数组内元素的数量必须是常量,不能是变量,这种时候可以用内存的动态分配int* A;A = (int*)alloc(n, sizeof(int));//动态分配数组,定义了一个n个元素的数组for (int i = 0; i < n; i++){*(A+i) = i + 1;}free(A);A = NULL;for (int i = 0; i < n; i++){printf("%d\n", A[i]);}return 0;
}

realloc函数

什么时候用?我用malloc或者alloc函数分配了一块内存,现在想改变内存块的大小,就可以用realloc函数。

alloc - void* malloc(size_t num, size_t size)

根据定义,有两个参数,第一个参数是指向已分配内存的起始地址的指针,第二个参数是新的内存块的大小

#include <stdio.h>
#include <stdlib.h>
/*用realloc函数,把内存块翻倍或者减半*/
int main()
{int n = 5;//printf("请输入一个数字\n");//scanf_s("%d", &n);//int A[n]; // 数组内元素的数量必须是常量,不能是变量,这种时候可以用内存的动态分配int* A;A = (int*)malloc(n* sizeof(int));//动态分配数组,定义了一个n个元素的数组for (int i = 0; i < n; i++){*(A+i) = i + 1;}//B是一个指针,也是一个数组int* B = (int*)realloc(A, (n/2)* sizeof(int));//会请求一块新的大小为2n的内存,然后把之前那个内存块的内容拷贝过去//int* B = (int*)realloc(A, 0); //相当于free(A);//int* B = (int*)realloc(NULL, n*sizeof(int)); //相当于malloc 不考虑之前的内存块,直接创建一个新的块/*如果请求的新块大于之前的块,且可以拓展之前的块,且能在之前块的基础上找到连续的内存,那么拓展之前的块否则分配新的内存把之前的块的内容拷贝到新块,然后去释放旧块的内存*/printf("A地址:%d  B地址:%d\n", A, B);for (int i = 0; i < n; i++){printf("%d\n", B[i]);}return 0;
}

realloc的两种等价表示方法

int* B = (int*)realloc(A, 0); //意思是把首地址A的这块申请的内存大小调整为0,也就相当于free(A);

int* B = (int*)realloc(NULL, n*sizeof(int)); //不考虑修改任何的内存块,直接创建一个新的块,也就相当于malloc函数

free()

任何分配了的动态内存在程序结束之前会一直存在(占据着内存),除非你显式地释放它。函数malloc\calloc\realloc分配的内存,要使用函数free来释放

free的入参是申请来的空间的起始地址

含义:把申请的来的空间还给系统

申请过的空间,最终都应该要还的,“出来混是要还的”。

一个显而易见的问题是,当我们用free释放了内存之后,我们还是可以继续访问那块内存。因为free的作用是释放空间,不一定抹掉数据。如果你知道地址,你可以看那个地址中存放的值,但是你应该只去读写分配给你的内存。如果那个地址不是分配给你的,你将无法知道你读写的地址上会是什么,不知道它的行为是什么。请确保只使用分配的内存,否则就像在黑暗中射击,不知道会发生什么。

内存泄漏

内存泄漏是不当地使用动态内存或者内存的堆区,在一段时间内持续增长

内存泄漏总是因为推中未使用和未引用的内存块才发生的

栈上的内存是自动回收的,栈的大小是固定的,最多就是发生栈溢出

函数返回类型是指针(地址)

一、函数可以返回一个数值,也可以返回一个地址

因此从函数返回指针的时候,我们需要小心它们的作用范围,我们必须保证地址没有被重用(用来存储其他东西)以及那个地址的数据没有被清除,大多数时候我们返回堆上分配的内存的地址,或者是全局区的地址

由于被调函数的栈空间总是在主调函数之上,任何时候被调函数执行的时候,主调函数仍然在栈内存当中,

从栈顶向下传一个局部变量或者一个局部变量的地址是不可以的。

如果我们在堆上有一个内存地址或者在全局区有一个变量,那么我们就可以安全地返回它们地地址

因此堆上分配内存需要显式释放,我们来控制它的释放(不像是栈是自动释放的)

而全局区的任何东西此如一个全局变量,它的生命周期是整个程序执行期间

我们可以使用malloc 或C++中使用new操作符来获取堆上的内存

#include <stdio.h>
#include <stdlib.h>
void PrintfHelloWorld() {printf("Hello World\n");
}
int* Add(int* a, int* b) //被调函数
{   //a和b是整型指针//printf("Add函数中a的地址为%d\n", &a);//printf("Add函数中指针变量a所存储的地址(main函数中整型变量a的地址)为%d\n", a);//printf("Add函数中存储在a里的地址上的值为%d\n", *a);int c = *a + *b;return &c; //返回一个地址
}
int main() //主调函数
{int a = 2, b = 4;//printf("Address of a in main = %d\n", &a);//int c = Add(a, b); //a,b的值会拷贝给Add,叫做函数按值传递int* ptr = Add(&a, &b); //a,b的地址会拷贝给Add,这种方式叫做传引用PrintfHelloWorld();printf("Sum = %d\n", *ptr); //此时运行程序,结果不是6。//是因为调用完Add函数后,c的地址上的数据由于调用了PrintHelloWorld函数后被抹除,即使PHW没有任何局部变量。/*由于被调函数的栈空间总是在主调函数之上,任何时候被调函数执行的时候,主调函数仍然在栈内存当中,从栈顶向下传一个局部变量或者一个局部变量的地址是不可以的。如果我们在堆上有一个内存地址或者在全局区有一个变量,那么我们就可以安全地返回它们地地址因此堆上分配内存需要显式释放,我们来控制它的释放(不像是栈是自动释放的)而全局区的任何东西此如一个全局变量,它的生命周期是整个程序执行期间我们可以使用malloc 或C++中使用new操作符来获取堆上的内存
}
#include <stdio.h>
#include <stdlib.h>
void PrintfHelloWorld() {printf("Hello World\n");
}
int* Add(int* a, int* b) //被调函数
{   //a和b是整型指针//printf("Add函数中a的地址为%d\n", &a);//printf("Add函数中指针变量a所存储的地址(main函数中整型变量a的地址)为%d\n", a);//printf("Add函数中存储在a里的地址上的值为%d\n", *a);int* c = (int*)malloc(sizeof(int)); //malloc*c = *a + *b;return c; //返回一个地址
}
int main() //主调函数
{int a = 2, b = 4;//printf("Address of a in main = %d\n", &a);//int c = Add(a, b); //a,b的值会拷贝给Add,叫做函数按值传递int* ptr = Add(&a, &b); //a,b的地址会拷贝给Add,这种方式叫做传引用free(c);PrintfHelloWorld();printf("Sum = %d\n", *ptr);
}

函数指针

问题:什么时候会用到函数指针

一、作用就是存储函数的地址

二、程序开始运行的时候,它会得到一块内存。程序结束运行的时候,它得到的的内存会自动回收

应用程序的代码段是用来存放从可执行文件拷贝过来的机器码或指令的,指令不是在第二存储介质(比如磁盘)上直接执行的,它们首先拷贝到主存然后才能执行。

源代码-->编译器进行翻译,成为二进制代码-->机器代码-->进入code区运行

三、我们不仅需要内存来存储指令,还需要内存来存储运行期间产生的很多数据,其他区段(global、stack、heap)主要是用来存储和管理数据的

四、

只使用函数名也会返回函数的地址

为了方便我们可以使用函数指针名来引用函数,就像使用函数名一样来直接返回地址

//Function Pointers in C/C++
//使用一个函数指针来引用一个函数
#include <stdio.h>
int Add(int a, int b) //函数功能:返回两个整数之和
{return(a + b); //返回a+b
}void main()
{int c;//变量声明int (*p)(int, int);//声明一个函数指针(存储函数的地址),1.返回类型必须与所要引用的函数的返回类型一致;参数也必须和函数的参数类型一致,。p = Add;//p指向了函数Addc = p(2, 3);//引用函数Add,参数肯定是自己设置;printf("%d", c); //输出c
}
//Function Pointers in C/C++
//使用一个函数指针来引用一个函数
#include <stdio.h>
void PrintHello() //函数功能:返回两个整数之和
{printf("Hello");
}void main()
{void (*p)();//声明一个函数指针(存储函数的地址),1.返回类型必须与所要引用的函数的返回类型一致;参数也必须和函数的参数类型一致。p = PrintHello;p(); //这里有点类似直接引用一个函数一样,只不过函数名换成了存储函数地址的函数指针的名称。
}

函数指针的使用案例(回调函数)

函数指针可以被用来作为函数参数,接收函数指针的这个函数,可以回调函数指针所指向的那个函数

#include <stdio.h>
int Compare(int a, int b) //函数功能:如果第一个参数大于第二个参数,返回1;否则返回-1
{if (a > b) return -1;else return 1;
}
int AbsoluteCompare(int a, int b) //函数功能:如果第一个参数的 绝对值 大于第二个参数的 绝对值 ,返回1;否则返回-1
{if (abs(a) > abs(b)) return 1;return -1;
}
void BubbleSort(int* A, int n, int (*Compare)(int,int))//函数功能:把一组数从小到大排列 参数:(以为数组地址,函数长度)
{int i, j, temp; //变量声明 i表示次数 j表示数组的下标 temp为交换值的变量for (i = 0; i < n - 1; i++) //外部的循环表示我们需要进行n-1轮。//进行一轮之后,最大的元素会浮到下标最大的位置//下一轮之后第二大的元素会浮到下标第二大的位置//如此反复进行,最后经过n-1轮之后这个数组就排好序了{for (j = 0; j < n - 1 - i; j++) //内部的循环是一轮的比较{if (Compare(A[j] , A[j + 1]) > 0) //返回-1,不满足判断条件,不执行下列语句。返回1,则交换数值。{temp = A[j];A[j] = A[j + 1];A[j + 1] = temp;}}}
}
int main(void)
{int A[] = {1,2,3,4,5,6};BubbleSort(A,6,Compare); //可以在这里选择大小排序还是绝对值大小排序for (int i = 0; i < 6; i++) printf("%d ", A[i]);return 0;
}
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int compare(const void* a, const void* b)
//void是通用的指针类型,我们可以转换为任何类型,这是qsort函数的规范
//const的含义是你不能去修改指针的值,我们需要void指针是因为qsort函数是一个通用的设计。记住qsort能够对任何数组排序,不仅仅是整型数组
{ int A = *((int*)a); //换成int*类型,并且得到数值int B = *((int*)b);//必须在A排名高的时候返回任何正数;A排名低的时候返回任何负数;排名相同的时候返回0。return abs(B)-abs(A);
}
int main(void)
{int A[] = { -31,22,-1,50,-6,4 }; //让数值以绝对值大小排列//bublesort这个函数只能接收一个整型的数组,但我们有一个库函数<stdlib>,可以接收任何数组,这样的话我们的函数就更通用了qsort(A, 6, sizeof(int), compare); //第一个参数是数组,第二个参数是数量,第三个参数是数据类型的大小,最后一个参数是函数指针 可以传入任何数组,字符型、整型、甚至是结构体for (int i = 0; i < 6; i++) printf("%d ", A[i]);return 0;
}

第九章 字符串

字符类型的占位符为%c

字符常量

字符串输出

puts(char);

for遍历

1 char类型

char(character):char类型用于存储字符,但是从技术层面看,char是整数类型,因为char存储的是整数而不是字符。

  • 用单引号括起来单个字符叫做字符常量'a''1'"T" //"T"是一个字符串
  • ''也是一个字符
  • printfscanf里用%c来输入输出字符
#include <stdio.h>
int main() {if (65 == 'A') { //字符常量是数字printf("OK");}return 0;
}

字符的输入和输出

输入字符,输出ascii码

#include <stdio.h>
int main(){char c;scanf("%c",&c);printf("c=%d\n",c); // 输出ASCII码printf("c='%c'\n",c); // 输出字符return 0;
} 

实际上,字符是以数值形式存储的,所以也可使用数字代码值来赋值

输入ASCII码,输出字符

#include <stdio.h>
int main(){int i;char c;scanf("%d",&i); //%d不能读char类型的c,所以要小小代换一下。c = i;printf("c=%d\n",c);printf("c='%c'\n",c);return 0;
} 

如何输入'1'这个字符给 char c?

  • scanf("%c", &c); // 输入 c
  • scanf("%d", &i); c=i; // 输入 49

if (49 == '1'){printf("OK");}//妙啊

一个49的各自表述!

char c;

c = 49; //

printf("%d %c", c, c); // %d是ASCII码表,%c是字符

混合输入

scanf("%d %c", &i, &c); // 有空格%d读到空格结束

scanf("%d%c", &i, &c); // 无空格

字符计算

一个字符+1:

char c = a;

c++;

printf("%c\n", c); // 得到码表中那个数字之后的字符

两个字符相减:

int i = 'Z' - 'A';

printf("%d\n", i); // 得到在码表中的距离

大小写转换

字母在ASCII表中是顺序排列的,并且大写字母和小写字母是分开排列的,并不在一起。

'a'-'A'是两段之间的距离

'a'-'A'可以把大写字母变成小写字母

'A'-'a'可以把小写字母变成大写字母

2 逃逸字符

\n

表示\n后面的东西从下一行开始输出

\"

\r

回车,表示这一行\r后面的东西吃掉\r前面的东西

\'

\b

在一行里面,\b后面的东西后退一格,\b前面有东西会吃掉

\\

\t

\t后面的东西到下一个表格位

3 字符数组与字符串数组

字符数组创建、输出

char word[]={ 'H', 'e', 'l', 'l', 'o', '!'};
for (int i = 0; i < 5; i++) {printf("%c ", word[i]);
}

char word[]{ 'H', 'e', 'l', 'l', 'o', '!'}; // 6个单元

word[0]

word[1]

word[2]

word[3]

word[4]

word[5]

H

e

l

l

o

!

如果最后一个单元是\0那就是字符串数组

char word[]{ 'H', 'e', 'l', 'l', 'o', '!', '\0'}; // 7个单元,字符串

word[0]

word[1]

word[2]

word[3]

word[4]

word[5]

word[6]

H

e

l

l

o

!

\0

字符串

0'\0'是一样的,但是和'0'不同

  • 0标志字符串的结束,但它不是字符串的一部分

    • 计算计算机长度的时候不包含这个0
  • 字符串以数组的形式存在,以数组或指针的形式访问
    • 更多是以指针的形式
  • string.h里面有很多处理字符串的函数

8.4.3-8.4.4没有记笔记

字符串变量


6 字符串输入和输出

字符串常量/字面量

定义:用""双引号括起来的内容成为字符串字面量。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串存储在内存中

字符串数组

char string[8]; // 8个字节的数组

scanf("%s", string);

printf("%s", string);

#include <stdio.h>
int main() {char string[8]; // 定义了字符串数组char string2[8]; // 定义了字符串数组//输入 hello world!scanf_s("%s", string,8); //scanf_s后面要用“,数字”写上字符串数组的长度。//读入hello,因为hello后面有空格,所以第一个%s读入helloscanf_s("%s", string2,8); //读入 world!printf("%s##%s##", string,string2);return 0;
}

scanf读入一个单词,到空格、tab、回车为止

scanf是不安全的,因为不知道要读入的内容的长度

安全的输入

char string[8];

scanf("%7s",string); // %8-1s

在%和s之间的数字表示最多允许读入的字符数量,这个数字应该比数组的大小小1

下一次scanf从哪里开始?交给下一个%s

常见错误 没听懂

char* string;

scanf("%s",string);

以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了

由于没有对string初始化0,所以不一定每次运行都出错

空字符串

char buffer[100]= "";

这是一个空的字符串,buffer[0] == '\0'

char buffer[]= "";

这个数组长度为1

scanf与scanf_s函数的使用 详解_许同学。。的博客-CSDN博客_scanf_s关于scanf与scanf_s

7 字符串函数

#include <string.h>

8.4.2字符串函数没记笔记

strlen

格式:strlen(const char*s);

返回字符串的长度

第十章 结构类型

建立结构声明

结构类型

不同的数据类型可以聚合在一起,生成一个新的数据类型(与int、char并列),使得代码在逻辑上更清晰(既然是一个数据类型,就可以定义相应的变量,即结构变量)

比如想构建一个人员信息系统:一个人的信息包括:姓名、性别、身高、体重。姓名char类型,性别是int类型,身高和体重时double类型

一个结构就是一个复合的数据类型,相当于聚合数据。

struct {int 变量名称;char 变量名称;...
} ; 

通常把结构声明放在所有函数的外面,作为全局变量

定义结构变量

结构别名

struct 结构别名{ };

结构别名的使用场景

此时可以取一个别名person代表每个人:struct person{ };

然后用struct person 结构变量名;声明多个结构变量

相当于先造一个模板,再用模板来生成一个个变量。

声明结构的形式

结构类型和结构变量是两件事情

声明一个结构类型、以及声明这种结构类型的变量

初始化结构

初始化列表由花括号包括。

花括号内为结构成员需要被初始化的值。

初始化的值按照结构成员声明时的顺序依次排列。

每个初始化值之间用 , 分隔。

结构数组

适用于管理系统类型的场景:一个元素有多个信息,并且要储存多个这样的元素

比如一本书的信息有书名、作者、字数,要储存多本书,就是一个图书管理系统

再比如一个人的信息有姓名、性别、身高、体重,要存储多个人的信息,就是人员管理系统

嵌套结构

嵌套结构是指一个所定义的结构类型里面再定义一个结构类型

初始化、输出

指向结构的指针

成员间接运算符 ->

(*pTimmy).name 等价于 pTimmy->name

结构在函数中传递

联合

在结构中会出现内存对齐的现象

联合内的成员共用同一个首地址,所以联合也叫做共用

有一种信息有三种形态,并且依次只能出现一种形态

一、格式

enum 枚举类型名字{名字0,...,名字n};

枚举

typedef简介

C语言允许为一个数据类型起一个新的别名,就像给人起“绰号”一样。

起别名的目的不是为了提高程序运行效率,而是为了编码方便。例如有一个结构体的名字是 stu,要想定义一个结构体变量就得这样写:

struct stu stu1;

1

struct 看起来就是多余的,但不写又会报错。如果为 struct stu 起了一个别名 STU,书写起来就简单了:

STU stu1;

1

这种写法更加简练,意义也非常明确,不管是在标准头文件中还是以后的编程实践中,都会大量使用这种别名。

————————————————

版权声明:本文为CSDN博主「风叶翩翩」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:C语言typedef的用法详解_风叶翩翩的博客-CSDN博客

程序结构

编译预处理和宏

源代码中,以#define开头的并不是c语言中的语句,它们属于预处理指令。

在代码被编译前,预处理器会先处理预处理指令,并根据预处理指令的意义,修改源代码。修改后的源代码,不会存储在源文件中,只会编译运行。

格式:#define 宏 替换体在程序运行过程中,会删除#define,并把代码中宏全部替换成替换体,替换体不一定是数字,只要能使代码正常运行就可以。

本质:把代码当作文本来处理,遇到宏就替换成宏对应的替换体。

带参数的#define

在#define中使用参数,可以创造作用与函数类似的宏函数

#define 宏(参数1,参数2,...,参数n) 替换体

宏函数的参数应当作为一个整体,优先运算

(宏函数展开后的表达式应 当作为一个整体

结合1、 2两点

如果宏函数的替换体内多次使用参数,请不要在宏函数的参数内填上自增、自减表达式

宏函数内两个有用运算符

井号:会用双引号包括参数

#define test(s) "s"

双井号: 将替换体中的两个记号组合成一个记号

取消宏定义

#undef NUM //取消宏定义

#include <stdio.h>int main3_1(void)
{#define MEAN(a,b) (a+b)/2int result;result = MEAN(2, 3); //result = (2+3)/2; printf("%d", result);return 0;
}int main3_2(void)
{#define SQUARE(x) ((x)*(x))int n = 2;n=SQUARE(2); //4n=SQUARE(2 + 2); //16n=100 / SQUARE(2); //25n=SQUARE(++n); //数值不确定return 0;
}
//井号:会用双引号包括参数
int main3_3() {
#define FMT(varname) "the value of " #varname " is %d\n"int number = 123;printf(FMT(number), number);return 0;
}
//双井号: 将替换体中的两个记号组合成一个记号
int main3_4() {
#define FMT(group,name) "The value of " #group #name " is %d\n"
#define VARNAME(group, name) group ## nameint group1Apple = 1, group1Orange = 2;int group2Apple = 100, group2Orange = 200;printf(FMT(group1, Apple), VARNAME(group1, Apple));printf(FMT(group1, Orange), VARNAME(group1, Orange));printf(FMT(group2, Apple), VARNAME(group2, Apple));printf(FMT(group2, Orange), VARNAME(group2, Orange));return 0;
}int main3_4() {
#define NUM 100
#undef NUM //取消宏定义
#define NUM 101printf("%d\n", NUM);return 0;
}

条件编译

//预编译时(就是在编译之前),会删除一些东西。
#include <stdio.h>
#define N 0//#if #endif #elif #else
int main5_1()
{
#if N == 1 //条件表达式必须为一个常量表达式 在编译前,由预处理器处理printf("111111\n");printf("222222\n");printf("333333\n");
#endif //标记指令块结束printf("AAAAAA\n");printf("BBBBBB\n");printf("CCCCCC\n");return 0;
}#include <stdio.h>#define N 0int main5_2()
{
#if N == 1 //条件表达式必须为一个常量表达式 在编译前,由预处理器处理printf("111111\n");printf("222222\n");printf("333333\n");
#elif N == 2printf("AAAAAA\n");printf("BBBBBB\n");printf("CCCCCC\n");
#elseprintf("******\n");
#endif //标记指令块结束return 0;
}//#ifdef #ifndef 测试一个宏是否被定义
//ifdef 如果定义了该宏,则保留指令块内的代码,否则删除指令块内的代码
//ifndef 如果没有定义该宏,则保留指令块内的代码,否则删除指令块内的代码
/*ifdef*/
#include <stdio.h>
#define printNumber
int main5_3()
{
#ifdef printNumberprintf("111111\n"); //会保留这三行输出,而下面的三行输出会删除printf("222222\n");printf("333333\n");
#endif
#ifdef printLetterprintf("AAAAAA\n"); //这三行会被删除printf("BBBBBB\n");printf("CCCCCC\n");
#endifreturn 0;
}
/*ifndef*/
#include <stdio.h>
#define printNumber
int main5_4()
{
#ifndef printNumberprintf("111111\n");  //被删除printf("222222\n");printf("333333\n");
#endif
#ifndef printLetterprintf("AAAAAA\n"); //被保留printf("BBBBBB\n");printf("CCCCCC\n");
#endif
}/*#if defined(宏) #elif defined(宏)*/
//测试一个宏是否被定义的另一种写法 #if defined(宏) #elif defined(宏)
#include <stdio.h>
#define printNumber
int main5_5()
{
#if defined( printNumber )printf("111111\n");  //被保留printf("222222\n");printf("333333\n");
#elif defined( printLetter )printf("AAAAAA\n"); //被删除printf("BBBBBB\n");printf("CCCCCC\n");
#elseprintf("******\n"); //被删除
#endifreturn 0;
}

大程序结构

多个源代码文件:一个主源文件、一个函数定义源文件、一个函数声明源文件

为什么使用include? 相当于函数声明库。

include还有哪些使用方式

预处理指令 #include

include <文件名> 在编译器的包含目录中搜索文件

#include "文件名" 现在当前目录中搜索文件,再到编译器的包含目录中搜索文件

对于stdio.h来说是编译器自带的文件,在编译器的包含目录中

多文件代码

/*尝试写一个简化版的print函数,并可以通过#include进行使用*/

/*

预处理:执行预处理命令,修改源代码

编译:将预处理后的源代码转换为 二进制目标文件

链接:将需要用到的目标文件合并成可执行文件

编译器对每个源文件独立编译,并生成对应的目标文件

.cpp -预处理-> .cpp -编译-> .obj(目标文件后缀名) --> 启动链接器

把声明写在一个文件中,文件无需编译,仅仅是被其他文件包含 --> 头文件.h

*/

标准头文件结构(头文件守卫)

头文件守卫的存在就是为了在同一个文件中不会重复地引用头文件,从而避免重复定义。

建议养成写头文件守卫地好习惯

#ifndef __LIST_HEAD__
#define __LIST_HEAD__#include "node.h"typedef struct _list{Node* head;Node* tail;
}List;#endif

运用条件编译,保证这个头文件在一个源文件中只会被包含依次

#pragma once 也能起到相同的作用,但不是所有的编译器都支持

存储类别

全局变量具有文件作用域,属于静态存储类别

声明在代码块内的变量都属于自动存储类型的变量

生命周期:数据对象从创建到销毁之间,数据对象存在的周期

作用域:标识符对数据对象指代关系存在的区域,它是一种关联关系

局部变量:自动变量块内作用域及声明周期

文件作用域内的静态变量

全局变量的初始化

没有做初始化的全局变量会得到0值

指针会得到NULL值

静态本地变量

在本地变量定义时加上static修饰符就成为静态本地变量

当函数离开的时候,静态本地变量会继续存在并保持其值

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>int f(void);int gAll = 12;int main(int argc, char const* argv[])
{f(); //3f(); //5 第二次时all没有被重新初始化为1f(); //7return 0;
}int f(void)
{static int all = 1;printf("int %s all= %d\n", __func__, all);all += 2;printf("agn in %s all = %d\n", __func__, all);return all;
}

静态本地变量的初始化只会在第一次进 入这个函数时做,以后进入函数时会保持上次离开时的值

静态本地变量实际上是特殊的全局变量

但是,全局变量有全局的作用域、全局的生存期。本地变量有本地的作用域和本地的生存区;而静态本地变量,有本地作用域,全局生存区

返回指针的函数

返回本地变量的地址是危险的

返回全局变量或静态本地变量的地址是安全的

返回函数内malloc的内存是安全的,但是容易造成问题

最好的做法是返回传入的指针、

不要使用全局变量来在函数间传递参数和结果

尽量避免使用全局变量

使用全局变脸和静态本地变量的函数是线程不安全的

多个源文件中不允许出现多个全局变量,但是可以共享一个全局变量

//static 仅能在本文件中使用

//extern 可以在多个源文件中共享使用

第十章 文件

如何保存程序中的数据,待下次打开程序就能复现数据呢?


创建并写入文件

fopen函数

创建或打开文件

FILE* fopen(const char* filename, const char* mode);

参数:( 文件路径(相对路径或绝对路径),操作模式 )

操作模式

读模式read:对文件进行读取操作

写模式write:对文件进行写入操作,即如果文件存在,则清空源文件的内容,如果不存在,则创建文件

返回值:文件指针(文件的地址)

示例:

//fopen("data.txt", "w"); //第一个参数表示相对路径,在当前目录下创建文件;

//fopen("F:/project/data.txt", "w"); //第一个参数表示绝对路径,在F盘,data文件夹下创建文件

读取文件

fprint函数

输出字符串到文件中

int fprintf(FILE* stream, const char* fomat, ...);

参数:(指明操作哪个文件)

fclose函数

fclose(fp)关闭fp指定的文件,必要时刷新缓冲区。 int fclose(FILE* stream);

int main1() {//创建文件FILE* pFile = fopen("data.txt", "w"); //w模式,对文件进行写入操作。如果存在,则清空内容;不存在则创建。if (pFile == NULL){return -1;}//定义数据int n = 123;double f = 3.1415;char ch = 'A';//--输出数据到到文件中--fprintf(pFile, "%d\n", n);fprintf(pFile, "%f\n", f);fprintf(pFile, "%c\n", ch);//关闭文件fclose(pFile); //fclose 关闭文件 int fclose(FILE* stream);return 0;
}

文本模式与二进制模式

回车:回到最左端

换行:下换一行

两种惯例:

第一种:沿用这两个动作,回车('\r')+换行('\n')

第二种:简化换行('\n')

C语言使用第二种管理,如果操作系统和C语言不一致,将会自动转换

windows系统使用了 \r \n,所以要自动转换

linux系统使用了 \n,无需转换

C语言在windows系统写入TXT文件时,会写入\r \n;而读取文件时,会将\r \n读取为 \n

C语言对文件输入输出的数据当作一行行文本进行处理,才会出现换行的自动转换现象。这种文件操作模式被称为文本模式

如果不希望C语言把数据当作文本处理,把\r \n进行自动转换,可以在打开文件时使用二进制模式,即在操作模式参数后加上b(binary 二进制的缩写)

读取文件

fscanf函数

读取文件

int fscanf (FILE* stream, const char* format, ...);

参数: (文件结构指针, 字符串,...)

返回值:返回参数列表中成功填充的参数个数;若文件读取失败或文件结尾将,返回EOF

fgetc函数

读取字符 int fgetc (FILE* stream);

参数是文件结构指针;

返回读取到的字符,到了文件结尾或者读取失败,返回EOF

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main2() {//--文件读取--FILE* pFile = fopen("data.txt", "r"); //r模式,对文件进行操作。if (pFile == NULL) {return -1;}//--定义变量--int n;double f;char ch;//--读取--fscanf(pFile, "%d", &n);fscanf(pFile, "%lf", &f);fgetc(pFile); //吸收上一行末尾的\nfscanf(pFile, "%c", &ch);//--数据输出--printf("%d\n", n);printf("%f\n", f);printf("%c\n", ch);//--关闭文件--fclose(pFile); //fclose 关闭文件 int fclose(FILE* stream);return 0;
}

EOF

当读取函数到达文件结尾,或出现读取错误时,函数会返回EOF。那么当返回了EOF时,如何区分这两种情况呢?

feof()和ferror()函数用于区分这两种情况

测试文件状态

feof函数

用于测试文件是否读写出错

int feof(FILE * stream);

参数:(文件结构指针)

返回值:如果文件结尾,返回值为非0;否则返回值为0。

ferror函数

int ferror(FILE * stream);

参数:(文件结构指针)

返回值:如果文件读或写出错,返回值为非0;否则返回值为0。


写入文件

fputc函数

向文件中写入(输出)一个字符

int fputc(int character, FILE* stream);

参数:(写入文件的字符,文件结构指针)

返回值:如果写入成功,返回刚刚写入的字符;如果文件结尾或失败,则返回EOF

fputs函数

向文件中写入一串字符串

int fputs(const char* str, FILE* stream);

参数:(要写入文件里的字符串,文件结构指针)

返回值:成功返回非负值;失败返回EOF

文件缓存区:当刷新缓存区时,内容才会保存到缓存区中。

刷新缓存:1.fclose函数 2.程序结束 3.主动调用fflush函数

fflush函数

主动刷新缓存

int fflush (FILE* stream);

参数:(文件结构指针)

返回值:刷新成功,返回0;否则返回EOF

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h> //提供system函数int main_6()
{//--变量声明--char str[] = "Have a good time\n"; //定义了一串字符串,要把这句话放到 data.txt 里面char* p = str;FILE* pFile;//--创建文件--pFile = fopen("data.txt", "w"); //w模式:先清空再写入if (pFile == NULL) //如果返回的值是空字符,那么就会返回-1{return -1;}//输出5次 Have a good timefor (int i = 0; i < 5; i++) //输出5次 Have a good time,也即重复5次向data.txt里打印输出这句话{fputs(str, pFile);}//刷新缓存区,然后执行暂停。此时已写入数据fflush(pFile);system("pause");fclose(pFile);return 0;
}

文件偏移

fseek函数

用于移动文件指针

int fseek(FILE* stream, long offset, int origin);

参数:(文件结构指针,文件指针偏移量,从什么位置开始偏移)第三个参数:SEEK_SET(文件开头) SEEK_CUR(当前文件位置) SEEK_END(文件结尾)

返回值:移动成功返回0;失败返回非零值。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{FILE* pFile = fopen("data.txt", "r");if (pFile == NULL){return -1;}char ch;fseek(pFile, 5, SEEK_SET); //从文件开头偏移5个字节ch = fgetc(pFile);putchar(ch);fseek(pFile, -5, SEEK_END); //从文件末尾偏移-5个字节 注意\r\nch = fgetc(pFile);putchar(ch);fclose(pFile);return 0;
}

rewind函数

将文件类型的指针回到文件最开始 1.fseek(pFile,0,SEEK_SET); 2.rewind(pFile)

ftell函数

获取当前指针的位置,也即返回文件的大小

long ftell (FILE* stream);

参数:(文件结构指针)

返回值:成功返回当前文件指针位置。如果失败返回-1.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{FILE* pFile = fopen("data.txt", "r"); //声明定义一个指向文件类型的指针变量if (pFile == NULL){return -1;}char ch;fseek(pFile, 0, SEEK_END); //将文件类型的指针偏移到末尾long length = ftell(pFile); //获取当前指针的位置printf("size of file %ld\n", length);fclose(pFile);return 0;
}

更新文件

H改为h:读取每一个字符,读到H就修改h

r模式读取文件、w模式写入文件

w+ 更新模式,可读可写。但是会清空文件原有内容

r+ 更新模式,可读可写。保留源文件内容

写转读:必须使用fflush fseek rewind其中一个函数

读转写:必须使用fseek rewind其中一个函数


将数值转为字符串保存

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>int main()
{//定义声明FILE* pf;int numbers[8] = { 1, 12, 123, 1234, 12345, 10, 123456, 1234567 }; //定义数组//打开文件pf = fopen("data.txt", "w");//向文件写入数组的值for (int i = 0; i < 8; i++){fprintf(pf, "%d\n", numbers[i]);}//关闭文件fclose(pf);return 0;
}

读取字符串转换为数值

//固定长度的循环
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>int main()
{//定义声明FILE* pf;int numbers[8] = {0};  //定义数组pf = fopen("data.txt", "r"); //读取模式//在文件读取数组的值for (int i = 0; i < 8; i++){fscanf(pf, "%d", &numbers[i]); //读取8个以换行为分隔的字符串}//在控制台输出刚才在文件中读取的值for (int i = 0; i < 8; i++){printf("%d\n", numbers[i]);}//关闭文件fclose(pf);return 0;
}
//还可以通过fscanf的返回值来判断是否已经读完文件了
//利用scanf返回值EOF来跳出while死循环
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void fileEofOrError(FILE* pFile);
int main()
{//定义声明FILE* pf;int numbers[8] = {0};  //定义数组int count = 0;int get;//打开文件pf = fopen("data.txt", "r"); //读取模式//在文件读取数组的值while(1){//如果data.txt文件中,数据数量超过8,则当数组填满时直接退出循环if (count >= 8){printf("numbers is full\n");break;}get = fscanf(pf, "%d", &numbers[count]); //返回成功读取数据的个数//输出fscanf返回的值printf("%d,", get); if (get == EOF){fileEofOrError(pf);break;}count++;}//在控制台输出刚才在文件中读取的值for (int i = 0; i < 8; i++){printf("%d\n", numbers[i]);}//关闭文件fclose(pf);return 0;
}

将数值以二进制形式保存

fwrite函数

把二进制数据写入文件

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);

参数:(待写入文件数据的首地址,每一块数据的大小,一共有多少块数据,文件结构指针)

返回值:成功写入多少块数据

以二进制形式保存成文件

从文件中读取二进制

fread函数

读取文件中的二进制数据

size_t fread(void* buffer, size_t size, size_t count, FILE* stream);

参数:(接收数据的首地址,每一块数据的大小,一共有多少块数据,文件结构指针)

返回值:成功读取多少块数据

位操作

十进制转换为二进制

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>void printBinary01(unsigned char dec);
void printBinary02(unsigned char dec);
void printBinary03(unsigned char dec);int main(void)
{for(int i = 0; i<17; i++)printBinary03(i); return 0;
}//十进制数转换为二进制数:顺序输出了余数
//被除数 unsigned char dec 长一字节 拥有8位二进制
void printBinary01(unsigned char dec)
{//除以二的商,余数unsigned int quotient;unsigned int remainder;while (dec > 0){remainder = dec % 2; quotient = dec / 2;dec = quotient;printf("%d", remainder);}
}//十进制数转换为二进制数:逆序输出余数
//unsigned char dec 长一字节 拥有8位二进制;
/*可以使用含有8个元素的数组存储二进制位,每位元素可以分别保存每位的二进制状态*/
void printBinary02(unsigned char dec)
{//--变量声明--int quotient;int remainder;char bits[8];int count = 0;//如果输入为零,则直接输出零,再结束函数if (dec == 0){printf("%d", 0);return;}while (dec > 0){remainder = dec % 2;quotient = dec / 2;dec = quotient;bits[count] = remainder;count++;}//如果输入的零,bits数组没有进行初始化,在上面加一个iffor (int i = count-1; i >= 0; i--){printf("%d", bits[i]);}putchar('\n');
}//十进制数转换为二进制数:逆序输出余数,显示所有二进制位数
//unsigned char dec 长一字节 拥有8位二进制;
/*让含有8个元素的数组初始化为0*/
void printBinary03(unsigned char dec)
{//--变量声明--int quotient;int remainder;char bits[8] = {0};int count = 0;while (dec > 0){remainder = dec % 2;quotient = dec / 2;dec = quotient;bits[count] = remainder;count++;}for (int i = 8 - 1; i >= 0; i--){printf("%d", bits[i]);}putchar('\n');
}

逻辑运算符

逻辑 与 &&

逻辑 或 &&

逻辑 非 !:如果运算对象为真,运算结果为假;如果运算对象为假,运算结果为真

位运算符

深入运算对象的内部,把运算对象的二进制位,根据非零值或零值,看作真或假,再进行逻辑运算

位逻辑 与 &

位逻辑 或 | :

如果两个位同时为假,运算结果为假,用数值0表示。否则,运算结果为真,用数值1表示

位逻辑 异或 ^: 两个位不相同,运算结果为真,用数值1表示。否则,结果为假,用数值0表示

位逻辑 非 ~:如果二进制位为真,运算结果为假,用数值0表示。如果二进制位为假,运算结果为真,用数值1表示。换句话说,要反转所有二进制位,1变为0;0变为1;

左移右移

左移运算符:将数据对象内部的二进制全部向左移动指定位,空出来的位置用0填充

右移运算符:将数据对象内部的二进制全部向右移动指定位;对于无符号类型:用0填充。有符号类型:取决于编译器

翁恺老师全套C语言课程笔记(本菜鸟正在学习)相关推荐

  1. java isprime函数_翁恺老师零基础java课程函数章第一题分解质因数

    分解质因数(5分) 题目内容: 每个非素数(合数)都可以写成几个素数(也可称为质数)相乘的形式,这几个素数就都叫做这个合数的质因数.比如,6可以被分解为2x3,而24可以被分解为2x2x2x3. 现在 ...

  2. MOOC浙大--翁恺老师的C语言编程练习7-1(多项式加法)

    题目内容: 一个多项式可以表达为x的各次幂与系数乘积的和,比如: 2x6+3x5+12x3+6x+20 现在,你的程序要读入两个多项式,然后输出这两个多项式的和,也就是把对应的幂上的系数相加然后输出. ...

  3. 浙大翁恺pat练习题_单词长度(翁恺老师C语言入门第八周测试题1)

    [更新]:写好程序后没在mooc的线上测试系统跑过,有部分格式上的bug,目前已经更新了,线上测试通过. 题目内容: 你的程序要读入一行文本,其中以空格分隔为若干个单词,以'.'结束.你要输出这行文本 ...

  4. 中国大学Mooc浙大翁恺老师《零基础学Java语言》编程作业(续)(5~ 7)

    感谢中国大学Mooc平台给我学习的机会,感谢翁恺老师深入浅出的讲解. 写此文为纪念我从无到有的学习和成长.便于以后回顾. 此答案仅供参考. 提交作业时需把 public class Homework_ ...

  5. 程序设计入门——C语言(翁恺老师)我的作业

    程序设计入门--C语言(翁恺老师)我的作业 第一周 程序设计与C语言 1 输出"Hello World"(5分) 题目内容: 请输出一行,内容为 Hello World 请注意大小 ...

  6. 中国大学Mooc浙大翁恺老师《零基础学Java语言》编程作业

    感谢中国大学Mooc平台给我学习的机会,感谢翁恺老师深入浅出的讲解. 写此文为纪念我从无到有的学习和成长.便于以后回顾. 另外: 此答案仅供参考,用于启发没有思路的同学.切勿照搬,自欺欺人. 提交作业 ...

  7. 【浙江大学 C++ 翁恺老师】学习笔记P1-P9

    文章目录 2 什么是面向对象 3 面向对象基本原理 4 自动售票机例子 5 头文件 6 时钟的例子 7&8 成员变量 9 构造和析构 浙江大学 C++ 翁恺老师 B站视频链接 2 什么是面向对 ...

  8. c++基础入门(根据浙大翁恺老师视频整理)

    把以前的笔记搬上来一下 根据浙大翁恺老师视频整理 https://www.bilibili.com/video/BV1yQ4y1A7ts?p=2 01第一个c++程序 02什么是对象 通过操作访问数据 ...

  9. 翁恺老师Java进阶城堡游戏源码

    刚开始学习Java,在比站发现了翁恺老师的课程,讲的很不错.Castle源码我找了很久都没有找到老师课上自己讲的那个程序,于是我决定做一回搬运工,自己敲了出来,现在也方便一下大家,大家一起学习,一起进 ...

最新文章

  1. 如果让你拥有100万粉丝,你会做什么
  2. 让Oracle SQL Developer显示的时间包含时分秒
  3. 填写各类表格时有时在多个选择前有小方框 在其中打勾
  4. mysql 临时表 插入_mysql临时表插入数据有关问题
  5. RotateAnimation 实现表盘指针转动
  6. Office无法打开超链接地址问题
  7. 条件查询_SQL简单查询(条件查询 模糊查询)
  8. 局部变量和参数传递的问题
  9. [转]Spring数据库读写分离
  10. 揭秘情场高手的三大套路
  11. 【Qt教程】3.2 - Qt5 event事件、定时器timerEvent
  12. WPF之HierarchicalDataTemplate(转)
  13. sqlmap指定cookie_Sqlmap Cookie注入 教程
  14. 英伟达发布《永劫无间》最新显卡驱动更新,驱动人生升级教程
  15. htmlcss系列学习——(五)css选择器
  16. 九宫格摆法_九宫格婚纱照摆法图片与技巧
  17. 【摸鱼吃瓜工作录】刚到公司,如何让项目经理对你刮目相看
  18. 面试第一问:简单做个自我介绍吧,怎么回答才让面试官频频点头?
  19. Word在试图打开文件时遇到错误,请尝试下列方法:检查文档或驱动器的文件权限 确保有足够的内存和磁盘空间 用文本恢器打开文件
  20. Linux下的进程PCB以及线程详解

热门文章

  1. 从程序员到系统分析员(转)
  2. 深入浅出Flask(10): H-ui前端框架入门之资源定位
  3. html5+JavaScript实现贪吃蛇游戏(可查看排行榜和个人历史记录)
  4. 七年级上册计算机重点知识点,七年级第一学期信息技术复习知识点1
  5. 由【酷狗音乐】所联想到的C/S界面设计
  6. 计算机学院研究生推荐信,美国留学推荐信范文:计算机教授研究生推荐信
  7. 联发科技推出曦力A系列 掀起智能手机科技普及革命
  8. 八字用神选择你的幸运色彩
  9. 查看家庭组组计算机用户名密码是什么,windows10系统查看家庭组密码的设置方法介绍...
  10. win系统在virtualBox虚拟机中连接不上网的问题解答方法