欢迎光临散文网 会员登陆 & 注册

小甲鱼C语言《带你学C带你飞》学习笔记(1)

2020-03-15 09:15 作者:康康Loong  | 我要投稿

视频链接https://www.bilibili.com/video/av27744141

由于篇幅限制,本笔记分两部分:

小甲鱼C语言《带你学C带你飞》学习笔记(1)-本文

小甲鱼C语言《带你学C带你飞》学习笔记(2)-传送门

本笔记是本人根据小甲鱼视频讲课内容记录,一些程序做了小修改。根据自身补充了一部分拓展内容。如有错误,欢迎一起讨论        ——正能量的康sir 

喜欢我的分享,不放点个关注、点个赞、投个币,谢谢:)               

P2第一个程序

编译型:c语言——》汇编——》机器语言——》CPU执行

解释型:java——》字节码——》解释器——》CPU执行

                                       

#include<stdio.h>

int main(){

printf("hello world!");

return 0;

}

 

linux环境下

编辑:vi test.c

编译:gcc test.c -o test

执行:./test

 

windows环境下

Dev C++

CodeBlocks

P3打印

printf("要打印的内容");

printf格式化输出函数,print打印,f即format格式化

\n换行

每行写不完行尾用\,下一行为上一行的延续。函数、关键字中间也可以用,注意下行开头如果有空格、缩进也会被解释,会导致识别不了。

转义字符


P4变量

确定目标并提供存放的空间

 

命名规则:

变量名只能是英文字母(A-Z,a-z)和数字(0-9)或者下划线(_)组成

第一个字母必须是字母或者下划线开头(不能是数字)

变量名区分大小写

不能使用关键字



32个关健字



1999年c99标准增加5个


2011年c11标准增加7个

 

数据类型

char字符型,占用1字节

int整型,通常反映了所用机器中整数的自然长度

float单精度浮点型

double双精度浮点型

 

声明变量语法

数据类型 变量名

如int a

P5常量和宏定义

常量:

整数常量

实型常量 小数等

字符常量 包括普通字符、转义字符

字符串常量

符号常量 使用之前必须先定义

 

定义符号常量 宏定义

#define标识符 常量

 

示例

#include<stdio.h>

#define NAME "小甲鱼"

#define YEAR 2010

int main (){

printf("%s成立于%d年",NAME,YEAR);

}

 

标识符:和关键字命名规则一样

 

字符串常量 使用null结尾,转义符是\0

P6数据类型

1.基本类型

整数型      int、short int、long int、long long int         (short int<=int<=long int<=long long int)

浮点型      float、double、long double

字符型      char

布尔型      _Bool

枚举型      enum

2.指针类型

3.构造类型

数组型

结构型

联合性

4.空类型

 

sizeof运算符

用于获取数据类型或表达式的长度

sizeof(变量或对象),可以不加括号,空格隔开。如sizeof(a)或sizeof a

sizeof(类型),如sizeof(int)

 

signed和unsigned类型限定符 限定char类型和任何整数类型的取值范围

signed带符号位,可以存放负数

unsigned不带符号位,只能存放正数和0

[signed] short [int]

unsigned short [int]

[signed] int

unsigned int

[signed] long [int]

unsigned long [int]

[signed] long long [int]

unsigned long long [int]

示例

#include<stdio.h>

int main(){

short i;

unsigned short j;

i=-1;

j=-1;

printf("%d\n",i);

printf("%u\n",j);

}

输出

-1

65535

P7取值范围

比特位

CPU能够读懂的最小单位——比特位,单位bit缩写b

字节

内存机构的最小寻址单位——字节,单位Byte缩写B

1Byte=8bit

1个字节等于8比特

1字节最大:二进制11111111 十进制255 十六进制FF

 

n个连续的1等于2的n次方减一

#include<stdio.h>

#include<math.h>

int main(){

int result = pow(2,32)-1;

printf("result=%d\n",result);

return 0;

}

 

符号位

存放signed类型的存储单元中,左边第一位表示符号位。如果该位为0表示该整数是一个正数;

如果该位为1,表示该整数是一个负数。

一个32位的整数变量,除去左边第一个符号位,剩下的表示值得只有31个比特位

事实上计算机是用补码的形式来存放整数的值。

正数的补码是该数的二进制形式

负数的补码:

(1)先取得该数的绝对值的二进制形式 -7  绝对值7的二进制10000111

(2)将第一步的值按位取反                                                           11111000

(3)最后将第二步的值加1                                                            11111001

补码的最大值127最小值-128

01111111          127

00000001          1

00000000          0

11111111          -1

11111110          -2

10000000          -128

 


基本数据类型的取值范围

 

P8字符和字符串

ASCII字符表

字符串:

声明字符串                     char变量名[数量];

赋值                                变量名[索引号]='字符';

声明+赋值 定义字符串 char name[5]={'a','b','c'};

 

#include<stdio.h>

int main(){

char a[9]={'b','i','l','i','b','i','l','i','\0'};

printf("%s\n",a);

return 0;

}

 

#include<stdio.h>

int main(){

//char a[8]={'b','i','l','i','b','i','l','i'};//错误 指定的数组长度不够时

//char a[]={'b','i','l','i','b','i','l','i'};//错误 不指定长度,每个字符用单引号时

//char a[9]={'b','i','l','i','b','i','l','i'};//正确 预留一个位置

//char a[]={'b','i','l','i','b','i','l','i','\0'};//正确 人工添加

char a[]={"bilibili"};//正确 字符串复制

printf("%s\n",a);

return 0;

}

 

拓展

'\0'是由C编译系统自动加上的。所以在用字符串赋初值时一般无须指定数组的长度, 而由系统自行处理。

把字符数组str1中的字符串拷贝到字符数组str2中。串结束标志'\0'也一同拷贝。

个案

1. 当数组长度不够。假设我们指定了数组长度,如:u8 str1[13]={"cxjr.21ic.org"};

由于字符组str1的长度为13,所以后面的信息会丢失,即'\0'丢失。

2. 如果在给数组赋值时,把每个字符单独用引号括起来。也会丢失'\0'。如:

u8 str1[]={'c','x','j','r','.','2','1','i','c','.','o','r','g'};

如果希望数组以'\0'结束,则可以写成以下三者之一:

u8 str1[]={"cxjr.21ic.org"}; //字符串赋值

u8 str1[]={'c','x','j','r','.','2','1','i','c','.','o','r','g','\0'}; //人工添加

u8 str1[14]={'c','x','j','r','.','2','1','i','c','.','o','r','g'}; //故意给数组预留一个空位

P9算术运算符

求余%后面必须是整数

 

 

 

 

 

 


1+2           +运算符,1、2两个操作数(双目)

 

表达式

用运算符和括号将操作数连接起来的式子

1+1

a+b

'a'+'b'

a+'b'+pow(a,b)*3/4+5

 

运算符的优先性和结合性

 

类型转换

1+2.0转化为1.0+2.0

强制转换

(int)1.2注意不会四舍五入

 

P10关系运算符和逻辑运算符

关系运算符  表示  比较:

优先级高:<、<=、>、>=

优先级低:==、!=

 

用关系运算符将两边的变量、数据、表达式连接起来,称为关系表达式。

1<2

a>b

a<1+b

'a'+'b'<='c'

(a=3)>(b=5)

结果是布尔型,c语言中正确1,错误0

 

逻辑运算符

非     !     优先级高   !a

与     &&   优先级中   a&&b

或     ||       优先级低   a||b

 

短路求值

又称最小化求值,是一种逻辑运算符的求值策略。只有当第一个运算数的值无法确定逻辑运算的结果,才对第二个运算数进行求值。

C语言对于逻辑与和逻辑或采用短路求值的方式。

#include<stdio.h>

int main(){

int a,b=3;

(a=0)&&(b=5);   // 因为a为0,0表示假,能确定逻辑与的值,不会再对第二个运算数求值

printf("a=%d,b=%d\n",a,b);

(a=1)||(b=5);      //因为a为1,1表示真,能确定逻辑或的值,不会再对第二个运算数求值

printf("a=%d,b=%d\n",a,b);

return 0;

}

结果

a=0,b=3

a=1,b=3

 

P11 if语句

语句(1):

if(表达式)

{

逻辑值为真所执行的语句、程序块

}

 

语句(2):

if(表达式)

{

逻辑值为真所执行的语句、程序块

}

else

{

逻辑值为假所执行的语句、程序块

}

 

语句(3):

if(表达式1) {……}

else if(表达式2){……}

else if(表达式3){……}

……

else if(表达式n){……}

else {……}

 

P12 switch语句和分支嵌套

 

switch(整型或字符型)

{

case常量表达式1:程序块1     break;

case常量表达式2:程序块2     break;

case常量表达式3:程序块3     break

…….

case常量表达式n:程序块n     break;

default:语句或程序块n+1  缺省一个break;可不写

}

 

悬挂else

if(a==1)

if(b==2)

printf("a等于1,b等于2");

else{lprintf("a不等于1")}

这样即使a等于1,b不等于2,也会输出a不等于1。因为else就近if

正确做法加{}

if(a==1)

{

if(b==2)

printf("a等于1,b等于2");

}

else{lprintf("a不等于1")}

 

等于号带来的问题

 

P13while语句和dowhile语句

 

入口条件循环

while(表达式){

循环体

}

 

出口条件循环

do

{

循环体

}

while(表达式);

 

 

P14for语句和循环嵌套

for(初始化表达式;循环条件表达式;循环调整表达式)

循环体

 

for(;;) 都可以省略 但是分号不能省

 

C99、C11允许在for语句的表达式1中定义变量           表达式1、3中可以用逗号运算符写多个语句

for(int i=0;i<100;i++)

P15break语句和continue语句

break跳出 跳出循环或switch。不能用于循环语句和switch语句之外的任何其他语句中。break 只能跳出一层循环。当有多层循环嵌套的时候,break只能跳出“包裹”它的最里面的那一层循环,无法一次跳出所有循环。同样,在多层 switch 嵌套的程序中,break 也只能跳出其所在的距离它最近的 switch。但多层 switch 嵌套实在是少见。

 

continue 结束本次循环,即跳过循环体中下面尚未执行的语句,然后进行下一次是否执行循环的判定。不能用于switch

P16拾遗

赋值运算符左边必须是一个Lvalue,变量名就是Lvalue

int a;

a=5

lvalue 识别和定位存储数值的标志符

 

复合的赋值运算符

+=

-=

*=

/=

%=

 

自增自减

i++

i—

i++、++i前后的区别

j=++i先i加1,再把加1后的值给j

j=i++ 先把i的值给j,i再加1

 

逗号运算符

优先级最低

表达式1,表达式,。。。,表达式n

运算过程从左到右

作为一个整体,它的值为最后一个表达式(表达式n)的值

a=(b=3,(c=b+4)+5)

先将b赋值为3,c赋值为b+4的和7,c的值加5,最后赋值给a,a的值12

常见:

1.        多个变量初始化

for表达式1、3

 

三目运算符

表达式1?表达式1:表达式3;

表达式1正确返回表达式2,错误返回表达式3

 

goto历史遗留 不常用

goto A;

A:      printf("123");

 

注释

//单行注释

 

/*多行注释

多行注释*/

 

P17数组

数组定义:

类型 数组名[元素个数]

int a[10];

int N=10;int a[N];

char b[24];

double c[3];

 

数组不能动态定义。[]中不能是一个变量,但是这个变量的值是固定的值的话可以

 

访问数组中的元素

数组名[下标]

第一个元素的下标是0,不是1

 

数组的初始化

1.所有元素赋值为0(只有0可以)

int a[10]={0};               //只是将第一个元素赋值0,后面会自动初始化为0

2.赋值不同的值用逗号隔开

int a[10]={1,2,3,4,5,6,7,8,9,0};

3.只给一部分元素赋值,未被赋值的元素自动初始化为0.

int a[10]={1,2,3,4,5}

4.只给出各个元素的值,不指定长度,由编译器自动判断

int a[]={1,2,3,4,5};

5.C99新特性:指定初始化的元素。未被赋值的元素自动初始化为0

int a[10]={[3]=110,[5]=120,[8]=114};

P18啪啪啪

数组不能动态定义。[]中不能是一个变量,但是这个变量的值是固定的值的话可以

 

P19字符串处理函数

字符数组

char str1[10]={'F','I','S','H','\0'};

char str2[]={'F','I','S','H','\0'};不指定长度

char str3[]={"FISH"};使用字符串常量初始化字符数组,可以省略大括号

char str4[]="FISH";

 

字符串处理函数 在<string.h>

strcat连接字符串

strcmp比较字符串

strcpy拷贝字符串strcpy(str1,str2)把str2复制给str1.注意\0也会复制。要保证str2长度小于str1

strlen获取字符串长度 数组元素的个数

strncat连接字符串(受限)

strncmp比较字符串(受限)

strncpy拷贝字符串(受限)strncpy(str1,str2,元素个数n) 把str2的n个元素复制给str1,要自己加\0 str1[n]='\0'

 

 

P20二维数组

定义:

类型 数组名[行数][列数]

访问:

数组名[行下标][列下标]

a[0][0]第一行第一列

 

二维数组初始化

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

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

int a[2][3]={{1},{4}}对每行第一个赋值,其余自动为0

int a[2][3]={0}整个二维数组初始化为0

int a[][3]={1,2,3,4,5,6} 行数可不写,自动判断

C99新特性 指定初始化的元素。

 

 

P21指针

通过变量名访问变量

指针和指针变量

类型名 *指针变量名

char *pa;定义一个指向字符型的指针变量

int *pb;定义一个指向整型的指针变量

 

获取某个变量的地址,可以使用取地址运算符&

char *pa=&a;

int *pb=&f;

 

如果需要访问指针变量指向的数据,可以使用取值运算符*

printf("%c,%d",*pa,*pb)

 

避免访问未初始化的指针(野指针)

#include<stdio.h>

int main(){

int *a;        //要初始化才对int *pa,a;*pa=&a;*pa=123                            但是字符串可以int *ps="haha"

*a=123;

return 0;

}

P22指针和数组

数组名是数组第一个元素的地址

 

指向数组的指针

char *p;

p=a;或p=&a[0];

 

对指针加减运算相当于指向距离指针所在位置向前或向后第n个元素

对比标准的下标访问数组元素,使用指针进行间接访问的方法叫作指针法

p+1并不是将地址加1,而是指向数组的下一个元素。(根据定义的类型长度加1个单位的int、char。。。)

 

P23指针数组和数组指针

int *p1[5]   指针数组 是数组 变量类型指针int *

int (*p2)[5] 数组指针 是指针 指向一个数组

 

定义数组指针int (*p)[3]=array

P24指针和二维数组

数组array[4][5]

*(array+1)==array[1]指向数组第二行第一个元素

*(array+1)+3==&array[1][3] 指向数组第二行第4个元素

结论

*(array+i)==array[i]

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

*(*(*(array+i)+j)+k)==array[i][j][k]

 

P25void指针和NULL指针

void指针 称为通用指针,可以指向任意类型的数据。也就是说任何类型的指针都可以赋值给void指针。

int a=1024;

int *pi=&a;

void *pv;

pv=pi;

 

NULL指针 用于指针和对象

#define NULL ((void *)0)

好习惯 不清楚将指针初始化为什么地址时,将她初始化为NULL

NULL不是NUL,NUL是空字符\0

P26指向指针的指针

int num=520;

int *p=&num;

int **pp=&p;两次解引用

好处:

避免重复分配内存

只需进行一次修改

 

P27常量和指针

常量:1,'a',2.5

或者

#define N 10

或者使用const修饰

const int price = 520

 

指向常量的指针

指针可以修改为指向不同的变量

指针可以修改为指向不同的变量

可以通过解引用来读取指针指向的数据

不可以通过解引用来修改指针指向的数据

 

常量指针

指向非常量的常量指针:

指针自身不可被修饰

指针指向的值可以被修改

指向常量的常量指针:

指针自身不可以被修改

指针指向的值也不可以被修改

 

指向"指向常量的常量指针"的指针

P28函数

函数的定义;

类型 函数名(参数列表)

{

         函数体

}

 

函数的声明

写在主函数mian前面

或者

在mian方法后写函数,在前面写函数声明

void f1(int);

int main(){

int x=1;

f1(x);

}

void f1(int x){

}

 

 

P29参数和指针

形参 形式参数

实参 实际参数

 

传值和传址

 

可变参数

 

P30指针函数和函数指针

指针函数int *f()

char *f1(char c){

return "字符串"

}

字符数组只要指名开头地址。结尾是\0自动判断

 

不要 返回局部变量的指针

 

 

函数指针int (*p)()

int square(int num){

return num*num;

}

int main(){

int num=5;

int (*fp)(int);

fp=square;函数名相当于地址 等价于fp=&square

printf("%d",(*fp)(num))

}

 

P31局部变量和全局变量

不同函数的变量无法相互访问

 

在函数里面定义的 局部变量

在函数外边定义的 全局变量

 

如果不对全局变量进行初始化,它会自动初始化为0

如果在函数内部存在一个与全局变量同名的局部变量,并不会报错,而是在函数中屏蔽全局变量(全局变量不起作用)

 

extern关键字 告诉程序变量在后面定义了,不要报错

 

不要大量使用全局变量,因为

1、全局变量从被定义开始,直到程序退出才能被释放,会占用更多的内存

2、污染命名空间,虽然局部变量会屏蔽全局变量,但这样一来也会降低程序的可读性,人们往往很难一下子判断出每个变量的含义和作用范围

 

 

P32作用域和链接属性

变量的作用范围 作用域

 

c语言编译器可以确认4种不同类型的作用域:

代码块作用域block scope

文件作用域file scope

原型作用域prototype scoope

函数作用域function scope

 

代码块作用域

在代码块中定义的变量,具有代码块作用域。从变量定义的位置开始,到标志该代码结束的右大括号}处

尽管函数的形参不在大括号内定义,但其同样具有代码块作用域,隶属于包含函数体的代码块

 

文件作用域

任何在代码块之外声明的标识符都具有文件作用域。从声明的位置开始,到文件的结尾处

函数名也具有文件作用域,因为函数名本身也在代码块之外

 

原型作用域

原型作用域只适用于那些在函数原型中声明的参数名。函数在声明的时候可以不写参数的名字(但参数的类型是必须要写上的),其实函数原型的参数名还可以随便写一个名字,不必与形式参数相匹配(当然,这样做毫无意义)。

void func(int a,int b,int c);

void func(int d,int e,int f){

}

 

函数作用域

函数作用域只适用于goto语句的标签,作用将goto语句的标签限制在同一个函数内部,以及防止出现重名标签。

 

定义和声明

当一个变量被定义的时候,编译器为变量申请内存空间并填充一些值

当一个变量被声明的时候,编译器就知道该变量被定义在其他地方

声明通知编译器该变量名及相关的类型已存在,不需要再为此申请内存空间。

局部变量既是定义又是声明

定义只能来一次,否则就叫做重复定义某个同名变量;而声明可以有很多次

 

链接属性

.c编译.o,.o链接lib库文件

external外部的 多个文件中声明的同名标识符表示同一个实体

internal内部的 单个文件中声明的同名标识符表示同一个实体static

none无 声明的同名标识符被当做独立不同的实体

只有具有文件作用域的标识符才能拥有external或internal的链接属性,其它作用域的标识符都是none属性

默认情况下,具备文件作用域的标识符拥有external属性。也就是说该标识符允许跨文件访问。对于external属性的标识符,无论在不同文件中声明多少次,表示的都是同一个实体

 

 

P33生存期和存储类型

c语言的变量拥有两种生存期

静态存储期

自动存储期

 

具有文件作用域的变量属于静态存储期,函数也属于静态存储期,属于静态存储期的变量在程序执行期间将一直占据存储空间,直到程序关闭才释放。

 

具有代码块作用域的变量一般情况下属于自动存储期。属于自动存储期的变量在代码块结束时将自动释放存储空间。

 

生存期

 

存储类型

指存储变量值的内存类型

auto

register

static

extern

typedef

 

自动变量auto

在代码块中声明的变量默认的存储类型就是自动变量,使用关键字auto来描述,可省略

#include<stdio.h>

int main(){

auto int I,j,k;

return 0;

}

 

寄存器变量register

将一个变量声明为寄存器变量,那么该变量就有可能被存放于CPU的寄存器中。

寄存器变量和自动变量在很多方面都是一样的,都拥有代码块作用域、自动存储期和空连接属性

不过将变量声明为寄存器变量,那么你就没有办法通过取址运算符获得该变量的地址。

 

静态局部变量static

static使得局部变量具有静态存储期,所以它的生存期与全局变量一样,直到程序结束才释放

 

作用于文件作用域的static和extern,static关键字使得默认具有external链接属性的标识符变成internal链接属性,而extern关键字是用于告诉编译器这个变量或函数在别的地方已经定义过,先去别的地方找找,不要急着报错。

 

typedef

 

P34递归

汉诺塔

谢尔宾斯基三角形

目录树的索引

女神的自拍

 

函数调用本身

 

递归必须有结束条件,否则程序将崩溃

#include<stdio.h>

void f1(){

static int count =10;

printf("HI\n");

if(--count) f1();

}

int main(){

f1();

return 0;

}

 

 

递归求阶乘

#include<stdio.h>

long fact(int num)                        //循环方法

{

                                                     long result;

                                                     for(result=1;num>1;num--)

                                                     {

                                                     result*=num;

                                                     }

                                                     return result;

}

long fact1(int num)                      //递归方法

{

                                                     long result;

                                                     if (num>0)

                                                     {

                                                             result=num*fact1(num-1);

                                                     }

                                                     else

                                                     {

                                                             result=1;

                                                     }

                                                     return result;

}

int main(void)

{

int num;

printf("请输入一个整数:");

scanf("%d",&num);

printf("%d的阶乘是:%d",num,fact1(num));

}                                                   

 

实现递归满足两个条件

调用函数本身

设置了正确的结束条件

 

普通程序员用迭代,天才程序员用递归,但我们宁可做普通程序员

 

 

P35汉诺塔

 递归求解汉诺塔

简单分为三个步奏:

1、将前63个盘子从X移动到Y上

2、将最底下的第64块盘子从X移动到Z上

3、将Y上的63个盘子移动到Z上

而1、3都能在此分解为类似的三步

1分为

1.1、将前62个盘子从X移动到Z上

1.2、将最底下的第63块盘子从X移动到Y上

1.3、将Z上的62个盘子移动到Y上

3分为

3.1、将前62个盘子从Y移动到X上

3.2、将最底下的第64块盘子从Y移动到Z上

3.3、将X上的62个盘子移动到Y上

.。。。

#include<stdio.h>

void hanoi(int n,char x,char y,char z) //将x上的n个盘子借助y移动到z上

{

         if(n==1)

         {

                  printf("%c-->%c\n",x,z);

         }

         else

         {

                  hanoi(n-1,x,z,y);

                  printf("%c-->%c\n",x,z);

                  hanoi(n-1,y,x,z);

         }

}

int main()

{

         int n;

         printf("请输入汉诺塔的层数:");

         scanf("%d",&n);

         hanoi(n,'X','Y','Z');

         return 0;

}

 

P36快速排序

分治法

大事化小,小事化了

 

快速排序

基本思想:通过一趟排序将待排序数据分割成独立的两部分,其中一部分所有元素均比另一部分的元素小,然后分别对两部分继续进行排序,重复上述步骤直到排序完成。

 

 

P37动态内存管理1

4个库函数stdlib.h

malloc申请动态内存空间

free释放动态内存空间

calloc申请并初始化一系列内存空间

realloc重新分配内存空间

 

malloc

函数原型void *malloc(size_t size);

向系统中申请分配size个字节的内存空间,并返回一个指向这块空间的指针。

函数调用成功,返回一个指向申请的内存空间的指针,由于返回的类型是void指针,所以它可以被转换成任何类型的数据;如果函数调用失败,返回值是NULL。另外,如果size参数设置为0,返回值也可能为NULL,但并不意味着函数调用失败。

#include<stdio.h>

#include<stdlib.h>

int main()

{

         int *ptr;

         ptr=(int *)malloc(sizeof(int));

         if(ptr==NULL)

         {

                  printf("分配内存失败!\n");

                  exit(1);

         }

         printf("请输入一个整数:\n");

         scanf("%d",ptr);

         printf("请输入的整数是:%d\n",*ptr);

         return 0;

}

 

free

函数原型:void free(void *ptr);

free函数释放ptr参数指向的内存空间。该内存空间必须是由malloc、calloc或realloc函数申请的。否则,该函数将导致未定义行为。如果ptr参数是NULL,则不执行任何操作。注意:该函数并不会修改ptr的值,所以调用后仍然指向原来的地方(变为非法空间)。

在以上程序中添加

free(ptr);

printf("请输入的整数是:%d\n",*ptr);//此时输出的值是无意义的

一个吃满内存的死循环

#include<stdio.h>

#include<stdlib.h>

int main()

{

         while(1)

         {

                  malloc(1024);

         }

         return 0;

}

 

内存泄露

如果申请的动态内存没有及时释放会怎么样?

c语言没有垃圾回收机制

导致内存泄露主要两种情况:

隐式内存泄露(用完内存块没有及时使用free函数释放)

丢失内存快地址

#include<stdio.h>

#include<stdlib.h>

int main()

{

         int *ptr;

         int num=123;

         ptr=(int *)malloc(sizeof(int));

         if(ptr==NULL)

         {

                  printf("分配内存失败!\n");

                  exit(1);

         }

         printf("请输入一个整数:\n");

         scanf("%d",ptr);

         printf("请输入的整数是:%d\n",*ptr);

         ptr=&num;//ptr又指向了别处,原来malloc申请的内存块丢失,free不能释放原来的动态内存。

         free(ptr);//另一个错误,free不能释放这里不是动态内存的ptr

         return 0;

}

P38动态内存管理2

malloc还可以申请一块任意尺寸的内存空间

#include<stdio.h>

#include<stdlib.h>

int main()

{

         int *ptr=NULL;

         int num,i;

         printf("请输入待录入的整数的个数:");

         scanf("%d",&num);

         ptr=(int *)malloc(num*sizeof(int));

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

         {

                  printf("请录入第%d个数:\n",i+1);

                  scanf("%d",&ptr[i]);

         }

         printf("你录入的整数是:\n");

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

         {

                  printf("%d ",ptr[i]);

         }

         free(ptr);

         return 0;

}

 

初始化内存空间

以mem开头的函数被编入字符串标准库,函数的声明包含在string.h这个头文件中

memset使用一个常量字节填充内存空间

memcpy 拷贝内存空间

memmove 拷贝内存空间

memcmp比较内存空间

 

memset示例

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#define N 10//没分号

int main()

{

         int *ptr=NULL;

         int i;

         ptr = (int *)malloc(N*sizeof(int));

         if(ptr==NULL)

         {

                  exit(1);

         }

         memset(ptr,0,N*sizeof(int));

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

         {

                  printf("%d ",ptr[i]);

         }

         free(ptr);

         return 0;

}

 

calloc

函数原型void *calloc(size_t nmemb,size_t size);

calloc函数在内存中动态地申请nmemb个长度为size的连续内存空间(即申请的总空间尺寸为nmemb *size),这些内存空间全部被初始化为0。

calloc函数与malloc函数的一个重要区别是:

calloc函数在申请完内存后,自动初始化该内存空间为零;malloc函数不进行初始化操作,里面数据是随机的。

所以下面两种写法是等价的:

calloc()分配内存空间并初始化

int *ptr=(int *)calloc(8,sizeof(int));

malloc()分配内存空间并用memset()初始化

 int *ptr=(int *)malloc(8*sizeof()int);

memset(ptr,0,8*sizeof(int));

 

memcpy示例

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

int main(){

         int *ptr1=NULL;

         int *ptr2=NULL;

         ptr1=(int *)malloc(10 *sizeof(int));

         ptr2=(int *)malloc(20 *sizeof(int));

         memcpy(ptr2,ptr1,10);

         free(ptr1);

         //略...对ptr2进行若干操作

         free(ptr2);

         return 0;

}

 

realloc

函数原型void *realloc(void *ptr,size_t size);

以下几点需要注意;

realloc函数修改ptr指向的内存空间大小为size字节

如果新分配的内存空间比原来的大,则旧内存块的数据不会发生改变;如果新的内存空间大小小于旧的内存空间,可能会导致数据丢失,慎用!

如果ptr参数为NULL,那么调用该函数就相当于调用malloc(size)

如果size参数为0,并且ptr参数不为NULL,那么调用该函数就相当于调用free(ptr)

除非ptr参数为NULL,否则ptr的值必须由先前调用malloc、calloc或realloc函数返回

 

 

P39C语言的内存布局


C语言的内存布局规律

根据内存地址从低到高分别划分为:

代码段(Text segment)

数据段(Initialized data segment)

BSS段(Uninitialized data segment)

栈(Stack)

堆(Heap)

 

代码段

代码段(Text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

 

数据段

数据段(Initialized data segment)通常用来存放已经初始化的全局变量和局部静态变量。

 

BSS段

BSS段(Bss segment/Uninitialized data segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称,这个区段中的数据在程序运行前将被自动初始化为数字0。

 

堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩展或缩小。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上;当利用free等函数释放内存时,被释放的内存从堆中被剔除。

 

大家平时可能经常听到堆栈这个词,一般指的就是这个栈。栈是函数执行的内存区域,通常和堆共享同一片区域。

 

堆和栈的区别

申请方式:

堆由程序员手动申请

栈由系统自动分配

释放方式:

堆由程序员手动释放

栈由系统自动释放

生存周期:

堆的生存周期由动态申请到程序员主动释放为止,不同函数之间均可自由访问

栈的生存周期由函数调用开始到函数返回时结束,函数之间的局部变量不能互相访问

发展方向:

堆和其它区段一样,都是从低地址向高地址发展

栈则相反,是由高地址向低地址发展

P40高级宏定义

宏定义

文件包含

条件编译

 

不带参数的宏定义

#define PI 3.14

为了和普通的变量进行区分,宏的名字通常我们约定是全部由大写字母组成

宏定义只是简单的进行替换,并且由于预处理是在编译之前进行,而编译工作的任务之一就是语法检查,所以编译器不会对宏定义进行语法检查

宏定义不是说明或语句,在末尾不必加分号

宏定义的作用域是从定义的位置开始到整个程序结束

可以用#undef来终止宏定义的作用域

#include<stdio.h>

#define PI 3.14

int main(){

         int r;

         float s;

         printf("请输入圆的半径;");

         scanf("%d",&r);

         //#undef PI

         s=PI*r*r;

         printf("圆的面积是:%.2f\n",s);

         return 0;

}

宏定义允许嵌套

#include<stdio.h>

#define PI 3.14

#define R 6371

#define V PI*R*R*R*4/3

int main(){

         printf("地球的体积是:%.2f\n",V);

         return 0;

}

 

带参数的宏定义

#define MAX(x,y) (((x)>(y))?(x):(y))

注意MAX没有空格(x,y)

#include<stdio.h>

#define MAX(x,y) (((x)>(y))?(x):(y))

int main(){

         int a,b;

         printf("请输入两个数:");

         scanf("%d%d",&a,&b);

         printf("较大的数是:%d\n",MAX(a,b));

         return 0;

}

括号不能省

#include<stdio.h>

#define SQUARE(x) x*x

int main(){

         int a;

         printf("请输入一个数:");

         scanf("%d",&a);

         printf("%d的平方是:%d\n",a,SQUARE(a));

         printf("%d的平方是:%d\n",a+1,SQUARE(a+1));//如果算x+1的平方会x+1*x+1,宏很傻,直接替换,不会帮你加括号 。保险做法#define SQUARE(x) (x)*(x),也不完美例如计算a++则(a++)*(a++)或造成a加两次

         return 0;

}

 

P41内联函数和一些鲜为人知的技巧

内联函数 解决程序中函数调用的效率问题。(但会增加编译时间)

定义函数前加上inline关键字

内联函数执行过程是在主函数中展开,而不是主函数-子函数-返回主函数。

现在的编译器很聪明,不写inline,也会自动将一些函数优化成内连函数

#include<stdio.h>

inline int square(int x)

{

         return x*x;

 }

int main(){

         int i=1;

         while(i<=100){

                          printf("%d的平方是:%d\n",i-1,square(i++)); //提高编译效率,也可以避免想宏定义出现两次加的错误

         }

         return 0;

}

 

 

#和##

两个预处理运算符

在带参数的宏定义中,#运算符后面应该跟一个参数,预处理会把这个参数转化为一个字符串。

#include<stdio.h>

#define STR(s) # s

int main(){

         printf("%s\n",STR(FISHC));

         return 0;

}

会把多个空格转化为一个空格

#include<stdio.h>

#define STR(s) # s

int main(){

         printf(STR(Hello   %s num=%d\n),STR(FISHC),520);

         return 0;

}

 

##运算符被称为记号连接运算符,比如我们可以使用

##运算符连接两个参数

#include<stdio.h>

#define TOGETHER(x,y) x ## y

int main(){

         printf("%d\n",TOGETHER(2,50));

         return 0;

}

 

可变参数

带参数的宏定义也可以使用可变参数

#define SHOWLIST(...) printf(#__VA_ARGS__)

其中…表示可变参数,__VA_ARGS__在预处理中被实际的参数集所替换(就像参数列表)(两边是两个下划线哦)。

#include<stdio.h>

#define SHOWLIST(...) printf(#__VA_ARGS__)

int main(){

         SHOWLIST(FishC,520,3.14\n);

         return 0;

}

可变参数允许空参数

#include<stdio.h>

#define PRINT(format,...) printf(#format,##__VA_ARGS__)

int main(){

         PRINT(num=%d\n,520);

         PRINT(Hello FishC!\n);//这个里面可变参数是空的

         return 0;

}

 

P42结构体

结构体声明:

struct 结构体名  //英语单词structure结构

{

结构体成员1;

结构体成员2;

结构体成员3;

...

};//这里有一个分号

示例

struct Book

{

char title[128];

char author[40];

float price;

unsigned int date;

char publisher[40];

};

 

定义结构体类型变量

struct结构体名称 结构体变量名;

或者

在声明结构体时定义

struct结构体名{

.。。。

} 变量名;//不过这时是全局变量

注意:如果

typedef struct结构体名称{

。。。

}简称;或typedef struct结构体名 简称;

使用typedef给结构体定义了一个简称,并不是变量

 

结构体可以放在函数外(全局),可以放在函数内(局部)

 

#include<stdio.h>

struct Book

{

char title[128];

char author[40];

float price;

unsigned int date;

char publisher[40];

} book;

int main(){

         //struct Book book;

         printf("请输入书名:");

         scanf("%s",book.title); //字符数组名指向开头元素地址 不用&

         printf("请输入作者:");

         scanf("%s",book.author);

         printf("请输入售价:");

         scanf("%f",&book.price);

         printf("请输入出版日期:");

         scanf("%d",&book.date);

         printf("请输入出版社:");

         scanf("%s",book.publisher);

         printf("\n===========数据录入完毕========\n");

         printf("书名:%s\n作者:%s\n售价:%.2f\n出版日期:%d\n出版社:%s\n",book.title,book.author,book.price,book.date,book.publisher);

}

 

初始化一个结构体变量

book={

"数学",

"小明",

21.5,

20200311,

"家里蹲大学出版社"};

 

c99新特性:初始结构体的指定成员值

结构体指定初始化成员使用点号运算符和成员名。

比如我们可以让程序只初始化Book的price成员;

struct Book book={.price=21.5};

多个可以不按结构体顺序进行初始化

struct Book book={.price=21.5,.title="数学"};

 

结构体的长度与内存对齐

#include<stdio.h>

int main(){

         struct A

         {

                  char a;

                  int b;

                  char c;

         } a={'x',520,'o'};

         printf("size of a=%d\n",sizeof(a));//结果结构体a长度为12 ,因为内存对齐(让CPU更快处理数据 )

         // 如果顺序为char a;char c;int b;则长度为8

         return 0;


}

拓展:扫码阅读 如何手工打包c结构体声明,减少内存空间占用

 

P43结构体数组和结构体指针

结构体嵌套

struct Date

{

int year;

int month;

int day;

};

struct Book

{

char title[128];

char author[40];

float price;

struct Date date;

char publisher[40];

} book={

"数学",

"小明",

21.5,

{2020,3,11},

"家里蹲大学出版社"};

.。。。

printf("日期:%d-%d-%d",book.Date.year,book.Date.month,book.Date.day);

。。。

 

结构体数组

声明方法

第一种

struct结构名称

{

结构体成员;

}数组名[长度];

 

第二钟

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

 

结构体数组初始化struct Book book[3]={{。。。},{。。。},{。。。}}

 

结构体指针

struct Book *pt;

pt=&book;

 

通过结构体指针访问成员

(*结构体).成员名 //*优先级低于点.   (*pt).title

结构体指针->成员名   pt->title

 

P44传递结构体变量和结构体指针

传递结构体变量

两个相同结构体类型的结构体变量可以赋值。book1=book2;

 

#include<stdio.h>

struct Date {

         int year;

         int month;

         int day;

};

struct Book {

         char title[128];

         char author[40];

         float price;

         struct Date date;

         char publisher[40];

};

struct Book getInput(struct Book book){

         printf("请输入书名:");

         scanf("%s",book.title);

         printf("请输入作者:");

         scanf("%s",book.author);

         printf("请输入售价:");

         scanf("%f",&book.price);

         printf("请输入出版日期:");

         scanf("%d-%d-%d",&book.date.year,&book.date.month,&book.date.day);

         printf("请输入出版社:");

         scanf("%s",book.publisher);

         return book;

 

}

void printBook(struct Book book){

         printf("书名:%s\n作者:%s\n售价:%.2f\n出版日期:%d-%d-%d\n出版社:%s\n",book.title,book.author,book.price,book.date.year,book.date.month,book.date.day,book.publisher);

}

int main(){

         struct Book book1,book2;

         printf("请输入第一本书的信息");

         book1=getInput(book1);

         printf("请输入第二本书的信息");

         book2=getInput(book2);

         printf("显示第一本书的信息");

         printBook(book1);

         printf("显示第二本书的信息");

         printBook(book2);

         return 0;

}

 

提高执行效率,函数可以不用结构体传址,而是传递指向结构体变量的指针。

#include<stdio.h>

struct Date {

         int year;

         int month;

         int day;

};

struct Book {

         char title[128];

         char author[40];

         float price;

         struct Date date;

         char publisher[40];

};

void getInput(struct Book *book){

         printf("请输入书名:");

         scanf("%s",book->title);

         printf("请输入作者:");

         scanf("%s",book->author);

         printf("请输入售价:");

         scanf("%f",&book->price);

         printf("请输入出版日期:");

         scanf("%d-%d-%d",&book->date.year,&book->date.month,&book->date.day);

         printf("请输入出版社:");

         scanf("%s",book->publisher);

}

void printBook(struct Book *book){

         printf("书名:%s\n作者:%s\n售价:%.2f\n出版日期:%d-%d-%d\n出版社:%s\n",book->title,book->author,book->price,book->date.year,book->date.month,book->date.day,book->publisher);

}

int main(){

         struct Book book1,book2;

         printf("请输入第一本书的信息");

         getInput(&book1);

         printf("请输入第二本书的信息");

         getInput(&book2);

         printf("显示第一本书的信息");

         printBook(&book1);

         printf("显示第二本书的信息");

         printBook(&book2);

         return 0;

}

 

动态申请结构体

使用malloc函数为结构体分配存储空间

修改

int main(){

         struct Book *book1,*book2;

         book1=(struct Book *)malloc(sizeof(struct Book));

         book2=(struct Book *)malloc(sizeof(struct Book));

         if(book1==NULL||book2==NULL){

                  printf("内存分配失败!\n");

                  exit(1);//需要stdlib.h

         }

         printf("请输入第一本书的信息");

         getInput(book1);

         printf("请输入第二本书的信息");

         getInput(book2);

         printf("显示第一本书的信息");

         printBook(book1);

         printf("显示第二本书的信息");

         printBook(book2);

         free(book1);

         free(book2);

         return 0;

}

篇幅限制,后面部分在这里——传送门



小甲鱼C语言《带你学C带你飞》学习笔记(1)的评论 (共 条)

分享到微博请遵守国家法律