当前位置:   article > 正文

C语言介绍_编译器不为int型分配内存

编译器不为int型分配内存

​​​​​​​ 

一、程序的入口

1.我们将写的代码交给CPU去执行,CPU只会执行程序中main函数中的代码,所以程序的入口是main函数,这个函数不能进行修改,一个标准的C程序中有且只能有一个main函数

2.CPU执行顺序是从上往下一句一句执行,只有在上一句执行完成之后才会执行下一句

3.这个程序什么时候结束?

main函数也是程序的出口,main函数执行完毕,程序结束

二、C语言注释

1.单行注释

// 双斜杠代表单行注释

2.多行注释

/* */ 中间的内容都是多行注释

3.快捷键

选中,然后command+/进行注释,这个注释不是/**/注释的快捷键,是在每行前面加//

三、数据存储

1.如果想要将数据存储在内存中

a.先看数据,看数据是1个什么类型的

b.根据数据的类型来挖坑

2.在内存中存储的数据的专业术语

如果我们要在内存中存储数据

1)就要先在内存中开辟1块空间

2)在开辟空间的同时,要指定这个空间的别名和类型

3)这个空间的类型决定了这个空间可以存储的数据的类型

常见的数据类型:

int 类型 整形

float 类型 单精确浮点型

double 类型 双精度浮点型

char 字符类型 

float 和double统称为浮点型或者实型

3.变量

1)变量:变量就是内存中开辟的用来存储数据的那块空间,所以变量是用来存储数据的

     变量的值:存储在变量所代表的那块空间的数据,就叫做变量的值

     变量的名称:为变量所代表的那块空间取的别名,叫做变量的名称

     变量的类型:变量所代表的那块空间的类型,就叫做变量的类型

    变量的本质:在内存中存储数据的那块空间

2)所以如果我们要在内存中存储数据,就只需要声明1个变量,声明1个变量,其实就是在内存中开辟了1块空间才存储数据

3)如何申明一个变量

数据类型 变量名称

int num;  // 当cpu执行这句代码的时候,CPU会在内存中开辟一块空间,空间的类型是int,空间的名称是num ,用这个空间来存储数据。

float num; // 当cpu执行这句代码的时候,CPU会在内存中开辟一块空间,空间的类型是float,空间的名称是num ,用这个空间来存储数据。

需要注意的是:编译的时候CPU不会分配内存空间,执行的时候才会分配内存空间

char ch;// 当cpu执行这句代码的时候,CPU会在内存中开辟一块空间,空间的类型是char,空间的名称是num ,用这个空间来存储数据。存储单个字符

我们声明1个变量,实际上就是在内存中开辟1个指定类型,指定别名的空间,变量就是这个空间

4.往变量所代表的空间存储数据

格式语法:

int num;

num = 100;

1)等号是赋值符号

赋值符号的作用:将右边的数据存储到左边的变量所代表的空间中

2)为int变量进行赋值

a.如果给的数据太大,直接报语法错误,

b.如果我们为int变量赋值了一个小数,只会存储这个小数的整数部分,不会做四舍五入

c.如果给的数据超过的范围并不多,这个时候存储的是一个随机值

3)为float类型的变量赋值

a.在c语言中如果我们直接写1个小数,那个这个小数的类型是double类型的,如果我们就是希望这个小数是1个float类型的,那么就在这个小数的后面加一个f/F,代表前面的这个小数的类型是1个float类型的

记住这句话:为float变量赋值的时候,赋值的小数后面要加1个f,精确存储的有效位数为7位,前面7位可以完美保存,7位之后的不能精确保存

4)为char类型的变量赋值

a.char变量中只能存储单个字符,并且这单个字符还得用单引号引起来

b.如果给的字符太多,保存最后1个字符

c.空格也是1个字符,所以可以存储1个空格

d.char变量中无法存储中文,在C语言中1个中文字符占用3个字节,而1个char变量占用1个字节

char ch;

ch = 'a';(对)

ch = '中'(错)

5.使用变量需要注意的问题

1)既生double,何生float?

double空间是float空间的两倍大小,所以数据如果本身有效位数不超过7位的话,使用double来存储就显得浪费

2)可以在声明变量的同时为变量赋值

数据类型 变量名称 = 数据;

int num1 = 20

代表声明了1个int类型的变量num,并为其赋值为10

3)如果我们要声明多个数据类型,可以使用批量声明的方式来简写,前提是类型相同

int num1 = 20,num2,num3 = 40;

4)赋值语句的规范

赋值符号的两边使用一个空格隔开,这个我们的代码看起来就比较好

5)垃圾值

我们声明了1个变量,如果没有为这个变量赋值,这个变量是有值的,是1个随机值,并不一定是0 ,而是1个随机数。

我们声明1个变量,没有为这个变量赋值,这个变量中是有值的,这个值叫垃圾值,值是不确定的。

所以为了保证我们的程序不会出意想不到的错误,要求声明1个变量,就应该为这个变量赋值1个初始值

6)变量可以重复赋值

为变量赋值的时候,如果变量中已经有值了,新值会将旧值覆盖掉,不会同时存在

变量的特点:喜新厌旧

7)为变量赋值的时候,可以将1个变量的值赋值给另外1个变量

  1. int num4 = 10;
  2. int num5 = num4;

这句话的意思是:将num1的值赋值给num2

变量之间的赋值原理:将源变量的值拷贝1份,将这个副本再赋值给目标变量,int num2 = num1;

将num1变量的值复制1份,将这个复制,赋值给num2变量,所以num2变量的值是10,num1的值还存在

8)使用变量之前,必须保证变量已经声明了

9)同一个变量不运行重复定义

10)变量的命名规范以及规则

变量的名称是由工程师自己定义的,变量的命名必须要遵守规则和规范

变量的命名规则,如果不遵守,编译器直接报语法错误

a.变量名只能以任意的字母、下划线、$开头,不能用数字开头

b.后面只能跟任意的字母、数字、下划线还有$

c.不能与C的关键字重名

d.C语言严格区分大小写

e.变量一定要先声明再使用

f.在同一个大括弧当中,不允许定义多个变量名相同的变量

变量的命名规范,你可以不遵守,编译器不会报错,可以执行,但是所有的程序员都在遵守

a.变量的名称要取得有意义,别人1看变量的名字就知道这个变量中存储的是什么数据

b.如果变量的名字是由多个单词组成的,第一个单词的首字母小写,其他单词的首字母大写

四、printf函数进行输出

1.双引号的内容原样输出

2.格式控制符输出

格式:输出变量的值

             printf("格式控制字符串",变量列表)

在格式控制字符串中如果有地方要显示变量的值,那么就使用1个占位符来占位,输出的变量的类型不同占位符是不一样的

如果输出的类型是int变量的,那么就使用%d占位符,%d代表的这个地方不要原样输出,而是输出1个整型变量的值?

哪个整型变量,就把整型变量输入到后面就可以了

如果输出的类型是float类型的,那么就使用%f占位符,默认输出小数点的后6位

 如果输出的类型是double类型的,那么就使用%lf占位符,默认输出小数点的后6位

如果输出的类型是char类型,那么就使用%c占位符

  1. //输出int类型的变量
  2. int num;
  3. num = 100;
  4. printf("num的值是%d\n",num);
  5. //输出float类型的变量
  6. float f1;
  7. f1 = 123.123f;
  8. printf("f1的值是:%f\n",f1);
  9. //输出double类型的变量
  10. double d1;
  11. d1 = 123.12;
  12. printf("d1的值是:%lf\n",d1);
  13. //输出char类型的变量
  14. char ch;
  15. ch = 'a';
  16. printf("char的值为:%c\n",ch);

输出结果为:

c.使用printf函数一次输出多个变量的值,一定要按照顺序来写,如果没有填写变量就会显示随机值,如果顺序混乱,那么输出的每个值也会进行混乱

 printf("num变量的值是%d,f1变量的值是%f,d1变量的值是%lf,ch变量的值是%c",num,f1,d1,ch);

3.print函数的高级输出

1)print函数的作用:向控制台输出信息

2)最简单的使用方法:

printf("输出信息");

双引号中的内容就会原样输出

3)输出变量的值:

务必要清楚占位符要使用哪一个占位符

int %d

float %f

double %lf 

char %c

4)printf函数的高级输出

%d用来输出int类型的值

a.%md  m是一个整数,指定输出的变量的值的位宽,输出的变量的值占多少个位

     %5d代码输出的整形变量的值一共占据5位,如果变量的值小于了5位,不足的地方就会以空格补齐,如果变量的值大于等于了5位,那么有多少位就显示多少位

  1. int num = 10;
  2. printf("num的值是:%5d\n",num);
  3. int num1 = 100101;
  4. printf("num1的值是:%5d\n",num1);

输出的结果为:

m也可以是1个负数,如果变量的值小于了指定的位数,不足的地方就会在后面以空格补齐

  1. int num2 = 15;
  2. printf("num2的值是:%-5d\n",num2);

输出的结果:

b.%0md m是1个整数 不足的地方就会以0补齐

  1. int num = 10;
  2. printf("num的值是:%05d\n",num);

 输出的结果:

%f和%lf的补充

a.默认情况下,只会输出小数点后面的6位

  1. float f = 12.5f;
  2. printf("f的值是:%f\n",f);
  3. double d = 14.24582457;
  4. printf("d的值是:%lf\n",d);

输出的结果为:

  

b.指定输出小数点后面的位数

%.nf 或者%.nlf   n是1个数字,代表要输出的小数点后面有多少位

  1. float f = 12.53f;
  2. printf("f的值是:%.2f\n",f);
  3. double d = 14.24582457;
  4. printf("d的值是:%.8lf\n",d);

 

五、第二天作业

1.请编写1个程序,定义变量保存1个人的年龄、身高、体重,然后输出这个人的信息,输出:我的年龄是XX岁,我的身高是XX,我的体重是XXKg

  1. int age = 24;
  2. float height = 1.6f;
  3. float weight = 45.2f;
  4. printf("我的年龄是%d岁,我的身高是%.1f米,我的体重是%.1fKg\n",age,height,weight);

输出的结果是:

 2.定义2个变量,分别存储一个人的年龄(28)和工资(7600.33),然后在屏幕上显示:大家好,我今天几岁了,我的工资是多少元。

  1. int age1 = 28;
  2. float salary = 7600.33f;
  3. printf("大家好,我今天%d岁了,我的工资是:%.2f元\n",age1,salary);

输出的结果是:

3.定义1个变量存储一个人的年龄(18),然后发现这个人的年龄不是18岁,要把这个人的年龄修改成20岁(本题体会变量可以被重复多次赋值),然后把这个人的年龄输出到屏幕上?

  1. int age2 = 18;
  2. age2 = 20;
  3. printf("age2的值是:%d\n",age2);

 

4.在屏幕上输出:我的手机  型号:998 价格:1550元  重量:0.3kg。请定义变量存储上面的值,并且显示在屏幕上。

  1. int type = 998;
  2. int price = 1550;
  3. float weight1 = 0.3f;
  4. printf("我的手机 型号:%d 价格:%d元 重量:%.1fkg\n",type,price,weight1);

输出的结果为:

5.请定义1个整型的变量,将这个整型的变量的值输出,要求:输出的整型的变量的数据如果不足8位,则在这个数据的前面以0补齐8位

  1. int number = 10;
  2. printf("number的值是:%08d\n",number);

输出的结果为:

 6.请定义1个double类型的变量,将这个double类型的数据输出。要求:小数部分只输出小数点后2位

  1. double doub = 1234.573469023;
  2. printf("doub的值是:%.2f\n",doub);

输出的结果为:

7. 改错题,请回答下面每一行代码是否有错误或者符合规范

1)int num1  = 2200000000;  (错误)

int只能存储-32768-32767中间的数据

2)int num2 = -19; (正确)

3) int num3 = 1.1;  (错误)

    int 只能存储整型的数据

4) double d1 = 12.12f; (错误)

    double不存储float类型的数据

5) float f1 = 13.57 (不规范)

    写一个小数默认是double类型的,所以要定义成float需要在后面加f

6) char ch = '哈'; (错误)

    char只能存单个字符,不能存文字,文字占用3个字节,char只存1个字节

7) char ch = 'a'; (正确)

8)float f2 = 1234561.56734f; (错误)

    float存储的有效位数为7位,这个已经超出了7位

8.请回答下面的变量名命名是否符合规则

num;  符合

age;  符合

large;  符合

_sum ;符合

%count ;不符合  只能以字母,下划线和$符开头

hehe;  符合

@cctv;不符合, 只能以字母,下划线和$符开头

te_e;  不符合,后面只能跟数字、字母、下划线和$符

comp uter ; 不符合,后面只能跟数字、字母、下划线和$符

 第三天


​​​​​​​

六、数据类型转换

1.如何修改target名称

我们创建一个项目,默认的target的名称跟项目的名称一致,如果我们想要修改target,有以下两种方式

1)修改target的名称。

只需要选中如下3个内容,直接按回车就可以修改,但是这种方式不建议,后期运行可能会报其他的错误

这个页面是在项目的Manager Schemes里面找到的

 2)直接删除target

直接删除上面的3个地方的内容就可以,这种方式推荐

2.数据类型转换

1)为变量赋值的时候,赋值的数据的类型必须要和变量的类型一致,否则就会出问题

   当我们为变量赋值的时候,如果赋值的数据的类型和变量的类型不一致,这个时候C系统会将赋值的数据的类型转换为变量的类型,然后在赋值。这种的情况叫做自动类型转换。

2)当变量的类型是int类型的时候

a.如果我们赋值的数据超出了int的范围,这个时候C系统会将数据转换为1个随机的int数据

b.如果我们赋值的数据超出了int的范围太多,这个时候自动类型转换无能为力,编译器直接报语法错误。

c.如果我们赋值的数据是1个实型的小数,这个时候C系统会直接截取整数部分

int num = 12.13,则会自动截取为12

3)当变量的类型为float类型的时候

a.如果我们赋值的数据是1个double类型的小数,这个时候C系统会将这个double类型的小数转换为float

b.如果我们赋值的数据是1个整数,那么就将这个整数转化为float小数,直接加1个.0就搞定了

4)当变量的类型是double类型的时候

a.当我们赋值的数据是1个float类型的时候,这个时候C系统就会将其转换为double,占据8个字节

b.如果我们赋值的数据是1个整数,那么就将这个整数转化为double小数,直接加1个.0就搞定了

5)当变量的类型是char类型的时候

ASCII 码:每一个字符数据都有1个与之对应的整数,这个整数就叫做这个字符的ASCII码

'A' 65         'a'  97       '0' 48

其他的字符的ASCII码就可以推出来

为char变量赋值的时候,可以直接赋值ASCII码,当我们为char变量赋值1个整数的时候,其实赋值的是以这个整数为ASCII码所对应的字符数据

  1. char ch = 'A';
  2. char ch1 = 65;

七、scanf函数的使用

1.scanf函数的作用

可以在程序运行的时候,让用户输入数据,然后将用户输入的数据直接赋值给指定的变量

2.语法格式

scanf("格式控制符",变量地址列表);

3.简单实用步骤

1)在格式控制符中使用占位符来要求用户输入1个指定类型的数据

2)在后面写上要将用户输入的数据存储到哪1个变量的地址,使用&就可以取到变量的地址

案例:

  1. int num = 0;
  2. int age = 0;
  3. printf("AAAA\n");
  4. printf("请输入qq号码:");
  5. scanf("%d",&num);
  6. printf("BBBB\n");
  7. printf("num的值是:%d\n",num);

scanf函数的执行原理:

a.scanf函数是1个阻塞式函数,当CPU执行到这个scanf函数的时候,CPU的执行就会暂停,不会继续往下执行了,并等待用户输入数据,当用户输入完毕数据按下回车表示输入完毕,这个时候,就会将用户输入的数据直接赋值给后面指定的变量,然后继续往下执行

注意:如果程序没有输出信息,下面的控制台输出区域不会自动弹出来的,如果直接写入scanf,上面没有printf的话,下面的控制台是不会输出的

4.使用scanf函数接收输入其他类型的数据

想要用户输入什么类型的数据,是根据格式控制符中的占位符来决定的

%d 用户输入int类型的数据

%f 用户输入float类型的数据

%lf  用户输入double类型的数据

%c 用户输入char类型的数据

1)先声明1个变量,变量的类型要和用户输入的数据的类型一致

2)使用scanf函数让用户输入数据:

a.在格式控制符中写上对应的占位符,一定要是对应的

b.要将用户输入的数据存储到哪一个变量中,就把这个变量的地址写在后面,使用&就可以获取到变量的地址

  1. //输入1个float函数的数据
  2. float f1 = 0.0f;
  3. printf("请输入1个float类型的数据:");
  4. scanf("%f",&f1);
  5. printf("你输入的数据是%f\n",f1);

5.scanf函数练习

询问用户你今年多大了,输出1句话,“这么巧啊,我今年也是XX岁."

  1. int age = 0;
  2. printf("你今年多大了\n");
  3. scanf("%d",&age);
  4. printf("好巧啊,我今年也是%d岁\n",age);

scanf函数

1)干嘛的,能实现什么效果?

是在程序运行的时候,让用户输入数据,并将数据存储到变量当中

2)如何实现这个效果?

a.先声明1个变量,用来保存用户输入的数据,这个变量的类型应该和要求用户输入的类型一致

b.使用scanf函数来接收用户的输入,scanf(“占位符和要求的数据的类型对应",&变量名);

c.当执行完毕之后,用户输入的数据就会被自动的保存在变量中

3)scanf函数后面不要加\n换行

4)scanf后面写变量的地址,不要直接写变量

6.使用scanf函数一次接收输入多个数据

1)如果用户输入的数据和要求的数据类型不一样,就会有点问题,如果要求的是int类型,但是输入的是小数,那么接收的就只是整数的部分,如果输入其他数据,字符,那么接收失败,就是默认值

2)为什么刚开始的时候需要默认值?

因为用户可能会输入其他数据,存储失败

3)如果要求用户输入的数据是1个数(实型或者是整型)

用户输入数据之前,输入的空格、回车、tab键 都会被自动忽略

4)使用scanf函数接收用户输入多个数据

a.在格式控制符当中写上多个占位符就可以,有多少个占位符就代表要让用户输入多少个数据,

占位符是什么类型的,就代表用户输入的数据的类型

b.在后面依次写上存储用户的数据的变量的地址

c.用户在输入数据的时候,可以使用空格或者回车来分隔多个数据

5)用户在输入多个数据的时候,默认的分隔符号是空格或者是回车

自定义分隔符:

在格式控制字符串中可以自定义多个输入数据的分隔符

scanf("%d-%d-%f",&classNum,&boyNum,&avg);

代表输入3个数据,这3个数据使用-分开

注意:一旦指定了分隔符,那么就必须使用指定的分隔符了,空格和回车就无法使用了

如果使用scanf函数一次输入多个数据,只能是数(整型和浮点型),如果有char混合输入,就会出问题

7.scanf函数缓冲区

1)在执行scanf函数的时候,会让用户输入一个数据,数据输入完毕,并不是将这个数据直接赋值给变量,而是先将数据存储在缓冲区,输入的任何数据都会存储在缓冲区里面

2)在执行scanf函数的时候,会先检查缓冲区是否有数据,如果缓冲区中没有数据,那么会让用户从键盘输入,如果缓冲区中有数据,直接从缓冲区中拿出来,不会让用户输入

3)当从缓冲区拿数据的时候,如果要拿的数据的类型是整型或者是实型,如果要拿到的是空格、回车、Tab键,就会自动忽略,继续往下拿,如果要拿的数据的类型是字符型,不会忽略任何数据

 

 4)输入一个整数,然后输入一个字符

  1. //1.输入1个整数
  2. int num = 0;
  3. printf("请输入第一个数:");
  4. scanf("%d",&num);
  5. printf("num = %d\n",num);
  6. //2.输入1个字符
  7. char ch = 'a';
  8. printf("请输入1个字符:");
  9. scanf("%c",&ch);
  10. printf("ch = %c",ch);

回车,空格也是1个字符,所以不用输入,直接从缓冲区中拿取。

 5)将缓冲区中的数据全部清空,用户就可以进行输入

//将缓冲区中的数据全部清空

    rewind(stdin);

  1. //1.输入1个整数
  2. int num = 0;
  3. printf("请输入第一个数:");
  4. scanf("%d",&num);
  5. printf("num = %d\n",num);
  6. //2.输入1个字符
  7. char ch = 'a';
  8. printf("请输入1个字符:");
  9. //将缓冲区中的数据全部清空
  10. rewind(stdin);
  11. scanf("%c",&ch);
  12. printf("ch = %c",ch);

6)输入多个值的情况

如果第一次输入的值不够下一次输入的值的数量,则还是会重新让用户进行输入,如果输入的值达到了下一次输入的值的数量,则会直接从缓冲区中取值

  1. //输出年龄
  2. int age = 0;
  3. printf("你今年多大了:");
  4. scanf("%d",&age);
  5. printf("好巧啊,我今年也是%d岁\n",age);
  6. // scanf("shu r")
  7. //使用scanf函数接收用户输入多个数据
  8. int num1 = 0, num2 = 0;
  9. printf("请输入两个变量的值:");
  10. scanf("%d%d",&num1,&num2);
  11. printf("num1 = %d num2 = %d\n",num1,num2);
  12. //让用户输入班级人数和男生人数和考试平均分
  13. int classNum = 0,boyNum = 0;
  14. float avg = 0.0f;
  15. printf("请输入班级人数、男孩数、平均分:");
  16. // scanf("%d-%d-%f",&classNum,&boyNum,&avg);
  17. scanf("%d%d%f",&classNum,&boyNum,&avg);
  18. printf("班级人数为:%d 男孩子的数量为:%d 平均分为:%.2f\n",classNum,boyNum,avg);

例如:上面我们让用户输入年龄,一同输入了两个数据,缓冲区中只有2个数据,一个空格,一个回车,不够第二次输入的2个数据,只剩下了1个,所以会让用户重新输入

但是如果我们输入3个数据,够第二次进行输入的数据,则不用输入,直接显示

八.交换两个变量的值 

1.声明1个第三方变量

  1. int num1 = 100;
  2. int num2 = 200;
  3. int temp = num1;
  4. num1 = num2;
  5. num2 = temp;
  6. printf("num1的值是:%d,num2的值是%d\n",num1,num2);

2.两数相加再相减

  1. num1 = num1 + num2;
  2. num2 = num1 - num2;
  3. num1 = num1 - num2;

3.异或

  1. num1 = num1 ^ num2;
  2. num2 = num1 ^ num2;
  3. num1 = num1 ^num2;

九、算术运算符

1.算术运算符,是在做算术运算的

+ 求数学和

- 求数学差

* 求数学积

/ 求数学商

% 求模运算符 求余数 10/3 商是3,余数是1

2.算术表达式

由算术运算符连接起来的式子,就叫做算术表达式

1+1

2*3

10/4

50-10

我们不能光秃秃的写1个表达式在我们的代码里,因为凡是表达式都有1个结果,一般情况下,我们要求要处理表达式的结果,处理的方式:将表达式的结果通过赋值符号存在1个变量中

int num = 10 + 10;

原理:先计算10+10这个表达式的结果,然后将这个表达式的结果赋值给num变量,所以num的值就是20

执行顺序:先申明int类型的变量,然后在计算10+10这个表达式的结果,最后将这个表达式的结果赋值给num变量

3.分解每1种算术运算符

1)+ 加法运算符 作用:求左右两边的数据的数学和

 10+34 参与算术表达式的数据叫做操作数,操作数可以是变量

2)- 减法运算符

3)*乘法运算符

4)/ 除法运算符 

  1. int num1 = 10;
  2. int num2 = 4;
  3. int num = num1/num2;
  4. printf("num的值是:%d\n",num);

比如:10/4,这个算术表达式的操作数都是int类型的,所以这个表达式的结果是2,不是2.5,所以正确的方式应该是使用int变量保存结果

如果就是要让结果为小数,可以把其他一个数变为double类型的,或者是将操作数乘以1.0,变成double,只能第一个数*1.0

  1. // double num3 = 10.0;
  2. // int num4 = 4;
  3. int num3 = 10;
  4. double num4 = 4.0;
  5. double num5 = num3 / num4;
  6. printf("num5的值是:%lf\n",num5);

  1. //除法运算符
  2. int num1 = 10;
  3. int num2 = 4;
  4. double num = num1 * 1.0 / num2;
  5. printf("num的值是:%lf\n",num);

 结果为2.5,num1*1.0是double类型的

 不能用最后那个乘以1.0

  1. //除法运算符
  2. int num1 = 10;
  3. int num2 = 4;
  4. double num = num1 / num2 * 1.0;
  5. printf("num的值是:%lf\n",num);

这个结果还是2,因为num1是int,num2是int,两数相除就是int类型的,乘以1.0还是2

5)% 求模运算 ,求余数

10 % 3 的结果是 10除以3的余数

看起来很简单,但是应用场景比较多,能做的事情也比较多

a.可以判断1个数是不是另外一个数的倍数

b.可以判断1个数能不能被另一个数整除

  1. int num1 = 10;
  2. int num2 = 4;
  3. int num6 = num1 % num2;
  4. printf("num的值是:%d\n",num6);

结果为2

判断1个年份是不是闰年,年份是否可以被400整除 year % 400

注意:

a.求模运算,实型数据无法参与求模运算,因为没有意义

b.m%n的结果一定是在0---(n-1)之间

4.算术表达式的结果的类型

1)算术表达式都有1个结果,一般处理方式是声明1个变量将这个表达式的结果存储起来。用来存储表达式的结果的变量应该声明成什么类型?我们必须要知道算术表达式的结果的类型,只有知道类型才可以声明1个对应的类型的变量来保存这个数据

2)如果参与算术表达式的操作数的类型都是一致的,那么这个算术表达式的结果的类型就是这个类型。

3)如果参与算术表达式的操作数的类型不一致,那么这个算术表达式的结果的类型就是范围最大的那个类型。

int < float < double

5.课堂练习

某超市衣服120.88一件,裤子89.9一条,让顾客输入自己买的衣服数量和裤子数量

1)显示他应该付多少钱

2)如果商家打折,打88折,在显示打折以后应该付款多少钱?

  1. float coatPrice = 120.88f;
  2. float pantsPrice = 89.9f;
  3. int coatNum = 0;
  4. int pantsNum = 0;
  5. printf("输入购买衣服的数量和裤子的数量:");
  6. scanf("%d%d",&coatNum,&pantsNum);
  7. float coatSumPrice = coatPrice * coatNum;
  8. float pantsSumPrice = pantsPrice * pantsNum;
  9. float sumPrice = coatSumPrice + pantsSumPrice;
  10. printf("总共应该付:%f元\n",sumPrice);
  11. float dazhePrice = sumPrice * 0.88;
  12. printf("打完折之后应该付:%.2f元\n",dazhePrice);

结果为:

​​​​​​​ 

6.算术运算符的优先级和char数据参与算术运算符

先乘除模,再加减,如果同级别,就会从左到右依次计算,使用小括弧可以改变运算的优先级。如果有小括弧,就先算小括弧中

7.char数据

char类型的数据可以参与算术运算。

当算术表达式中的操作数是1个char数据的时候,会先将这个char数据的ASCII码取出来代替,然后再参与算术运算,所以,如果操作数是1个char类型的,实际上他是1个int类型的数据在参与运算.

  1. char ch = 'a';
  2. int nu = 10;
  3. int sum = ch + nu;
  4. printf("sum的值是:%d\n",sum);

  1. char ch1 = 'A';
  2. char ch2 = 'a';
  3. int sum1 = ch1 + ch2;
  4. printf("sum1的值是:%d\n",sum1);

8.大小写转换 

如果是大写转成小写,只需要加32,注意接收变量是char类型,如果是小写转大小,需要减32

  1. char ch3 = 'A';
  2. char ch4 = ch3 + 32;
  3. printf("ch4的值是:%c\n",ch4);
  4. int ch5 = ch3 + 32;
  5. printf("ch5的值是:%d\n",ch5);

需要注意的就是如果要是char类型的,转成字母,需要用char类型的变量来接收,如果是数字,需要用int类型的来接收

9.复合赋值运算符 

1.)要想将1个变量的值在自身的基础之上增加指定的数。

num的值就是在自身的基础之上+2

  1. int num = 10;
  2. num = num + 2;
  3. printf("num的值是:%d",num);

2.)让1个变量的值在自身的基础之上增加指定的数,简写方式

使用复合赋值运算符:+=  

num += 2完全等价于num = num + 2;

  1. int num = 10;
  2. // num = num + 2;
  3. num += 2;
  4. printf("num的值是:%d\n",num);

3.)-= 

 *=

/=

%=

4.)如果以后想要改变1个变量的值,是在自身基础之上做改变的,那么就可以使用复合赋值运算符

10.自增与自减运算

1)自增运算符

++

2)自增表达式

前自增表达式

int num = 1;  ++num;

后自增表达式

int num = 1; num ++;

无论是前自增表达式还是后自增表达式,都是将自身的值增加1

3)自增表达式是1个表达式,既然是1个表达式,那么这个自增表达式就一定有一个结果,那么我们就可以使用1个变量把这个表达式的结果存储起来。

后自增表达式的计算方式:

先将自身的值取出来作为后自增表达式的结果,然后在将自身的值加1;

前自增表达式的计算方式:

先将自身的值+1,然后再将自身的值取出来作为表达式的结果

  1. int main(int argc, const char * argv[]) {
  2. int i = 0;
  3. int j = i++;
  4. printf("i的值是%d,j的值是%d\n",i,j);
  5. return 0;
  6. }


​​​​​​​

  1. #import <Foundation/Foundation.h>
  2. int main(int argc, const char * argv[]) {
  3. int i = 0;
  4. int j = ++i;
  5. // i = i++;
  6. // printf("i的值是%d\n",i);
  7. printf("i的值是%d,j的值是%d\n",i,j);
  8. return 0;
  9. }

4)自增运算符的优先级比算术运算符的优先级要高

5)练习一:

  1. int main(int argc, const char * argv[]) {
  2. int i = 1;
  3. int j = i++ + ++i + i++ + ++i + i++;
  4. // i++计算完成之后i的值是2,表达式的结果是1
  5. //++i之后i的值是3,表达式的结果是3
  6. //i++之后,i的值是4,表达式的结果是3
  7. //++i之后,i的值是5,表达式的结果是5
  8. //i++之后,i的值是6,表达式的结果是5
  9. //所以是1+3+3+5+5 = 17
  10. printf("j的值是%d\n",j);
  11. return 0;
  12. }

6)练习二:

  1. int i = 1,j = 2;
  2. int k = i++ + --j + j++ + --i;
  3. //i++计算完之后i的值是2,表达式的值是1
  4. //--j计算完之后j的值是1,表达式的是1
  5. //j++计算完之后j的值2,表达式的值是1
  6. //--i计算完之后i的值1,表达式的值是1
  7. //所以表达式的结果是1+1+1+1 = 4
  8. printf("k的值是%d\n",k);

11.逗号表达式 

1)逗号在C语言中也是1个算术运算符

2)逗号表达式:

就是由逗号连接起来的1个式子,用逗号将其他的表达式连起来,就叫做逗号表达式

语法格式:

表达式1,表达式2,表达式3,表达式4,.......表达式n

3)逗号表达式的执行结果和步骤

从头到尾的去执行每1个子表达式,最后1个子表达式的结果就是整个逗号表达式的结果

4)逗号表达式的目的,并不是想要前面表达式的结果,而只是想要前面的表达式执行,要最后1个表达式的结果。前面的表达式只是想要让其执行,不关心结果,因为前面的表达式一旦执行就会影响最后1个表达式的结果,而我们关心的是前面的变化完了以后,最后1个表达式的结果是多少

5)练习

  1. int main(int argc, const char * argv[]) {
  2. int i = 0,j = 1,k = 2;
  3. int num = (i++,++i,(++i,k++,++j),i++ + k++);
  4. printf("num = %d\n",num);
  5. return 0;
  6. }

12.比较运算符和表达式 

1)符号

>大于    <小于      >=大于等于

<= 小于等于   ==等于   !=不等于

2)比较表达式

由比较运算符连接起来的式子,就叫做比较表达式

3)比较表达式的结果的类型

1个比较表达式代表:1个条件

而1个条件的结果:成立或者不成立,真的或者假的

所以比较表达式的结果,要么是真,要么是假,没有第3个结果了

在C语言中使用int类型的数据来表示真假

0代表假 ,非0代表真,所以我们应该使用1个int类型的变量来保存1个比较表达式的结果,如果比较表达式所描述的条件是成立的,那么这个比较表达式的结果就为1,如果比较表达式所描述的条件是不成立的,那么这个比较表达式的结果为0

  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. // printf("Hello, World!\n");
  4. int num1 = 10;
  5. int num2 = 20;
  6. int res = num1 > num2;
  7. printf("res的值是:%d\n",res);
  8. return 0;
  9. }

13.比较运算符介绍 

1)> 大于运算符,如果左边的数据大于了右边的数据,结果就真,结果就是1,否则为0

2) >= 大于等于运算符,如果左边的数据大于或者等于右边的数据,那么条件成立,结果就为1,否则为0

3)< 小于运算符 如果左边的数据小于右边的数据,那么条件成立,结果为1,否则为0

4)<= 小于等于运算符  如果左边的数据小于或者等于右边的数据,那么条件成立,结果为1,否则为0

5)== 相等比较运算符 ,如果左边的数据等于右边的数据,那么条件成立,结果为1,否则为0

友情提示:判断两个数据是否相等是两个等号,1个等号叫做赋值

6)!= 不等比较运算符,如果左边的数据不等于右边的数据,那么条件成立,结果为1,否则就为0

14.常见的比较运算表达式的使用场景

1)比较运算符的两边可以是常量,也可以是变量,还可以是1个表达式

如果表达式的1边是一个表达式,那么会先将这个另外的表达式的结果计算出来,然后在做比较运算

比较运算符的两边也可以都是1个表达式,先将两个表达式的结果算出来,然后在做比较运算

  1. #include <stdio.h>
  2. int main(int argc, const char * argv[]) {
  3. // insert code here...
  4. // printf("Hello, World!\n");
  5. int num1 = 100;
  6. int num2 = 200;
  7. int res = num1 + num2 > 300;
  8. printf("res的值是:%d\n",res);
  9. return 0;
  10. }

2)判断1个表达式的结果和另外1个数据之间的关系

a.写1个表达式,判断num变量是否为5的倍数

int num = xx;

num % 5 == 0;

b.写1个表达式,判断小明的语文数学和英语的平均成绩是否合格

  1. int chinese,math,english;
  2. int res1 = (chinese+math+english)/3 >= 60;

15.char数据也可以参与运算

  1. int num1 = 100;
  2. char ch = 'a';
  3. int res2 = num1>ch;

char数据也可以参与比较运算,那么比的是相应的ASCII码,让用户输入两个字符,都是小写字母,

char ch1,ch2;

ch1<ch2;

16.第三天作业

1) 接受用户从键盘输入的两个字符,然后输出他们

  1. char ch,ch1;
  2. printf("请输入两个字符:");
  3. scanf("%c%c",&ch,&ch1);
  4. printf("输入的两个字符为:%c,%c\n",ch,ch1);

输入的两个字符直接输入就可以,不用用空格隔开,不然空格会作为输入符显示

2)接受用户从键盘上输入的两个双精度浮点数,然后输出他们

  1. double d1 = 0.0 ;
  2. double d2 = 0.0;
  3. printf("请输入两个双精度浮点数,以空格隔开:");
  4. scanf("%lf%lf",&d1,&d2);
  5. printf("输入的两个双精度浮点数为:%.2lf,%.2lf\n",d1,d2);

3)接受用户从键盘上输入的两个单精度浮点数,然后输出他们(保留两位小数) 

  1. float f1 = 0.0f;
  2. float f2 = 0.0f;
  3. printf("请输入两个单精度浮点数,以空格隔开:");
  4. scanf("%f%f",&f1,&f2);
  5. printf("输入的两个单精度浮点数为:%.2f,%.2f\n",f1,f2);

 4)用户从键盘上输入两个整数,然后输出他们和

  1. int a = 0;
  2. int b = 0;
  3. printf("请输入两个整数:");
  4. scanf("%d%d",&a,&b);
  5. int c = a + b;
  6. printf("输入的两个整数的和为:%d\n",c);

5)用户从键盘上输入两个整数,然后输出他们的差

  1. int a = 0;
  2. int b = 0;
  3. printf("请输入两个整数:");
  4. scanf("%d%d",&a,&b);
  5. int c = a - b;
  6. printf("输入的两个整数的差为:%d\n",c);

6)用户从键盘上输入两个整数,输出他们的商

  1. int a = 10;
  2. int b = 5;
  3. printf("请输入两个整数:");
  4. scanf("%d%d",&a,&b);
  5. int c = a / b;
  6. printf("输入的两个整数的商为:%d\n",c);

7) 用户从键盘上输入两个整数,输出他们的余数

  1. int a = 0;
  2. int b = 0;
  3. printf("请输入两个整数:");
  4. scanf("%d%d",&a,&b);
  5. int c = a % b;
  6. printf("输入的两个整数的余数为:%d\n",c);


​​​​​​​

8)用户输入矩形的长和宽,求出矩形的面积和周长,并将结果显示在屏幕上

  1. int height = 0;
  2. int width = 0;
  3. printf("请输入矩形的长和宽:");
  4. scanf("%d%d",&height,&width);
  5. int area = height * width;
  6. int girth = (height + width)*2;
  7. printf("长方形的面积为%d,周长为%d\n",area,girth);

9)要求用户输入圆形的半径,求出圆形的面积和周长,并将结果显示在屏幕上

  1. float radius = 0.0f;
  2. printf("请输入圆的半径:");
  3. scanf("%f",&radius);
  4. float area = 3.14 * radius * radius;
  5. float girth = 2 * 3.14 * radius;
  6. printf("圆的周长为:%f,圆的面积为%f\n",area,girth);

10)编程实现计算几天(如46天)是几周零几天,天数要求用户从控制台输入,请用户输入1个天数,计算用户输入的天数是几周零几天

  1. int day = 0;
  2. printf("请输入1个天数:");
  3. scanf("%d",&day);
  4. int weak = day / 7;
  5. int yuday = day % 7;
  6. printf("%d天是%d周零%d天\n",day,weak,yuday);

 11)请用户从控制台输入1个秒数,求用户输入的秒数是几天几小时几分几秒?

  1. int second = 0;
  2. printf("请输入一个秒数:");
  3. scanf("%d",&second);
  4. int day = second /(24 *60*60);
  5. int hour = (second %(24 *60*60))/(60*60);
  6. int mintue = (second %(24 *60*60))%(60*60)/60;
  7. int second1 = (second %(24 *60*60))%(60*60)%60;
  8. printf("%d是%d天%d小时%d分%d秒\n",second,day,hour,mintue,second1);

 12)编一个程序,定义圆周率pai = 3.14,要求用户输入圆柱的底圆的半径和圆柱的高,求出圆柱的面积。圆柱的体积:pai *圆的半径的平方*圆柱的高

  1. int radius = 0;
  2. int height = 0;
  3. printf("输入圆柱的半径和高:");
  4. scanf("%d%d",&radius,&height);
  5. float area = 3.14 * radius *radius *5;
  6. printf("圆柱的面积为%f\n",area);

13)裤子的单价是78.8元,衬衫的单价是45.6元,输入顾客购买的裤子数量和衬衫数量,显示顾客应该付多少钱?

  1. float pants = 78.8f;
  2. float shirt = 45.6f;
  3. printf("输入购买的裤子数量和衬衫数量:");
  4. int pantsCount = 0;
  5. int shirtCount = 0;
  6. scanf("%d%d",&pantsCount,&shirtCount);
  7. float sumPrice = pants * pantsCount + shirt * shirtCount;
  8. printf("顾客应该付%.2f元\n",sumPrice);

 第四天

十、复习

1.scanf函数的使用

1)作用

2)语法格式

3)使用正确的占位符:

%d  %f  %lf  %c

4)如何一次性接收用户输入多个数据,前提:只能是数

5)scanf函数缓冲区

rewind(stdin) 清除缓冲区

2.交换两个变量的值

1)声明第3方临时变量,转圈

2)两数相加再相减

3)算术运算符和算术表达式

a.算术表达式的结果的类型

b.char类型也可以参与算术运算,用其对应的ASCII码

3.复合赋值运算符

int i = 10; i+=2;// i = i+2;

4.自增与自减运算

++

自增表达式

int i = 1; 前:++i 后:i++

5.逗号表达式

7)比较运算符和比较表达式

> >= < <= == !=

比较表达式:条件

int i = 10>3;

十一、逻辑表达式

1.比较运算符与表达式

1)比较表达式:1个比较表达式描述的是1个条件,那么可以根据这个比较表达式的结果来判断这个条件是否成立

缺点:这个比较表达式只能描述1个条件

2)在代码世界中同样有这样的需求:判断多个条件并且指定多个条件之间的关系

int num = ?

判断:num的值是不是在0~100的范围

num>=0并且num<=100

2.逻辑运算符

1)作用:判断多个条件,使用逻辑运算符来指定多个条件之间的关系

2)逻辑运算符:

&& 逻辑与 并且

|| 逻辑或 或者

!非 取反

3.逻辑表达式

由逻辑运算符连接起来的式子,就叫做逻辑表达式

一般情况下:逻辑运算符连接起来的是两个条件(比较表达式)

  1. int num1 = 10;
  2. int num2 = 20;
  3. num1>0 && num2>10;

逻辑表达式的结果:

因为逻辑表达式描述的是多个条件,也是条件,所以结果要么成立,要么不成立,所以逻辑表达式的结果仍然是真假,所以我们使用int类型的变量来保存逻辑表达式的结果

0 假

非0 真

逻辑表达式与比较表达式的区别:

a.比较表达式只能描述1个条件

b.逻辑表达式可以描述多个条件,并且使用逻辑运算符来指定多个条件之间的关系

4.详解逻辑运算符

1)&& 逻辑与 并且

由&&连接起来的逻辑表达式,只要当两边的条件都成立的时候,整个逻辑表达式才成立

,只要有1边的条件不成立,那么整个逻辑表达式都不成立

1 && 1 1

0 && 1 0

1 && 0 0

0 && 0 0

断路问题:

逻辑表达式在执行的时候,是先计算左边的条件的结果,再计算右边的条件的结果。当是&&逻辑表达式的时候,如果左边的表达式不成立,那么这个时候就可以确定整个逻辑表达式的结果为不成立,那么这个时候,右边的条件根本不会去进行判断,所以这个时候右边的那个条件根本就不会去执行了

  1. int i = 1;
  2. int res = i++>10 && ++i<0;
  3. printf("i的值是%d\n",i);
  4. printf("res的值是%d\n",res);

结果为:

i++之后i的值为2,但是结果2>10不成立,所以后面不会执行,i的最终值为2

2)

|| 逻辑或 或者

由||连接起来的逻辑表达式,只要有1边的条件成立,那么整个逻辑表达式就成立

只有两边都不成立的时候,整个逻辑表达式才不成立

1||1 1

1||0 1

0||1 1

0||0 0

断路问题:逻辑表达式在执行的时候,是先计算左边的条件的结果,再计算右边的条件的结果,当是||逻辑表达式的时候,如果左边的条件成立,那么这个时候就可以确定整个逻辑表达式的结果了,为成立

那么这个时候,右边的条件根本就不会去判断了,所以这个时候右边的那个条件根本就不会去执行

3)! 非 取反

作用:将真变为假,将假变为真

int res = 10>0

int res = !res;

!的作用 :将后面的值取反,如果后面为真,结果就为假,如果后面为假,那么结果就为真。

需要注意的是定义一个int类型的变量为10,如果给他取反的话为0,不是-10

int a = 10 ; int b = !10,那么b的值为0

4)优先级:

逻辑运算符的优先级比比较运算符的优先级要高。

5)逻辑运算符之间的优先级

not and or

! &&  ||

! > && > ||

取反的优先级最高,其次是逻辑与,最小的优先级是逻辑或,

当你不确定优先级的时候可以使用小括弧来改变优先级

5.课堂练习

1)定义1个变量year,int,保存年份,写1个表达式,判断这个年份是不是闰年

满足如下两个条件之一,那么年份就是闰年。

a.年份可以被400整除,

b.年份可以被4整除,但是不能被100整除

  1. int year = 0;
  2. printf("请输入1个年份:");
  3. scanf("%d",&year);
  4. int res = year % 400 == 0 || (year %4 == 0 && (year %100 !=0));
  5. printf("res的值是:%d\n",res);

2)请用户输入小明的语文成绩和数学成绩,输出以下判断的结果

a.两门成绩都大于90分

b.任意1门成绩大于90分

  1. int chineseGrade = 0,mathGrade = 0;
  2. printf("请输入小明的语文成绩和数学成绩:");
  3. scanf("%d%d",&chineseGrade,&mathGrade);
  4. int res = chineseGrade > 90 && mathGrade>90;
  5. int res1 = chineseGrade>90 || mathGrade>90;
  6. printf("res的值是:%d\n",res);
  7. printf("res1的值是:%d\n",res1);

6.运算符的优先级和结合性

1)到目前为止,我们已经学习了

== 赋值运算符

+ - * / % 算术运算符

+=   -=   *=    /=     %= 复合赋值运算符

++   -- 自增自减运算符

, 逗号运算符

> >= < <= == != 比较运算符,关系运算符

&& || ! 逻辑运算符

& 取地址运算符

C语言中的表达式可以由多种运算符来参与共同运算

2)C语言将所有的运算符分为了15个等级

C语言的算术优先级_Victor_psl的博客-CSDN博客_c语言算术运算符的优先级顺序

此处引用了优先级博客,有侵权请联系删除

1级的优先级最高,15级的优先级最低

当表达式中有多种运算符,先算优先级较高的,再算优先级较低的

3)运算符的结合型

当1个表达式中的运算符的优先级是一样的时候,是从左往右计算,还是从右往左计算,是根据运算符的结合方向来的

十二、IF结构

1.比较表达式和逻辑表达式,统称为条件表达式

条件表达式,描述的是1个或者是多个条件,我们写1个条件表达式的目的,是根据这个结果做出不同的反应

2.在代码世界中

有1段代码,并不是非要执行,而是只有在满足某个条件的时候才执行,否则就不执行

3.IF结构

1)作用:可以实现1段代码只有在满足指定的条件的时候执行,否则这段代码不执行

2)语法:

if(条件表达式)

{

   执行代码;

}

条件表达式:比较表达式或者逻辑表达式

3)执行步骤:

a.先判断if后面的条件表达式的真假

 a.1 如果为真,条件成立,就会执行if块中的代码,执行完毕之后再继续往下执行

a.2如果为假,条件不成立,会略过if块中的代码继续往下执行

4)什么时候使用IF结构呢?

当你有1段代码,不是非要执行,只是在满足某个条件的时候才会执行,否则就不执行,那么这个时候就可以使用IF结构

如何使用?

if(表达式)

{

 满足条件要执行的代码

}

5)课堂案例:

判断李凯的钱包里的钱是否有100块,如果有100块,下课请吃饭

  1. int main(int argc, const char * argv[]) {
  2. int price = 101;
  3. if (price >= 100) {
  4. printf("有钱,下课请吃饭\n");
  5. }
  6. return 0;
  7. }

6)课堂练习:

请用户输入他儿子的语文、数学、英语成绩,如果平均分及格>=60,就奖励1个吻

  1. int chineseGrade = 0, mathGrade = 0,englishGrade = 0;
  2. printf("请输入儿子的语文成绩、数学成绩、英语成绩,以空格隔开:");
  3. scanf("%d%d%d",&chineseGrade,&mathGrade,&englishGrade);
  4. float pingjunGrade = (chineseGrade + mathGrade +englishGrade)/3.0f;
  5. printf("儿子的平均成绩是:%.2f\n",pingjunGrade);
  6. if (pingjunGrade>=60) {
  7. printf("平均分合格,奖励1个吻");
  8. }

7)使用IF结构注意

1)关于分号

在1条语句的后面,我们都要写1个分号,这个分号代表这条指令的结束,CPU在识别指令的时候,如何判断指令结束并执行,以分号为基准

而绝大数情况下,每1条语句就代表1个指令,所以每条语句的后面就要加1个分号

IF结构是1个语法块,它的范围是从{开始,到}结束,右大括弧代表语法块的结束,所以,一般情况下,IF结构不需要加分号,因为}就代表语法块的结束,就算要加,你也应该加在结束的大括弧后面 

2)IF后面的小括弧当中其实任意的表达式都是可以写的,反正在执行的时候,计算IF后面的小括弧中的表达式的结构

0 代表假

非0 代表真

虽然这样是可以的,但是这样没有什么意义,一般情况下,这里面我们还是写的条件表达式

3)永真和永假的问题

if(1) 代表条件永远成立

if(0) 代表条件永远不成立

4)IF块中可以有任意行的代码,只要符合逻辑

5)请保持正确的代码缩进

属于1个大括弧的代码,就应该在这个大括弧保持1个Tab缩进,选中要对齐的代码:control+i 就会自动对齐,前提是代码没有语法错误

十三、变量的作用域

1.变量就是在内存中用来存储数据的那块空间

声明1个变量就是在内存中开辟1块空间用来存储数据,那么申请开辟的空间需要回收吗?肯定需要,如果不回收一直占用空间内存

2.声明在1个大括弧中的变量,当这个大括弧被CPU执行完毕之后,定义在这个大括弧中的变量,就会被系统立即回收

所以,定义在1个大括弧中的变量,只能在这个大括弧中访问,因为这个大括弧还没有结束,这个变量都是在。出了这个大括弧,就无法访问定义在这个大括弧中的变量了,因为大括弧一结束,定义在大括弧中的变量就会被系统立即回收

出了大括弧,num被系统自动回收了,所以报错,无法访问

变量的作用域:

指的就是变量可以在哪段范围访问,离变量最近的大括弧

3.我们可以自定义一个作用域来提前释放1个变量 

  1. {
  2. int num = 10;
  3. printf("num的值是:%d",num);
  4. }

如果后面没有地方在用到这个变量可以提前释放,节省内存

4.在不同的作用域(大括弧)中,或者是不同层级的作用域中,是可以定义名称相同的变量的

  1. int num = 1;
  2. if (num > 0) {
  3. int num = 10;
  4. printf("num的值是:%d",num);
  5. }
  6. printf("num的值是:%d",num);

这个时候,内层作用域中的同名变量就会被外层作用域中的同名变量屏蔽,在内层作用域中访问,访问的就是内层的,在外层访问,就是外层的

虽然,在不同的层级的作用域中可以定义名称相同的变量,但是这样的代码比较混乱,所以一般情况下,我们不会在不同的层级的作用域中定义相同的变量

十四、IF-ELSE结构

1.IF的缺点

只能在条件满足的时候执行1段代码,条件不满足的时候就无法执行另外一段代码了

新的需求:

我们希望条件满足的时候执行一段代码,条件不满足的时候执行另外1段代码,虽然我们可以用两个IF结构来实现,但是这样的话,效率低下,因为会判断两次条件

2.IF-ELSE结构

1)作用:在条件满足的时候执行1段代码,在条件不满足的时候执行另外1段代码

2)语法:

if(条件表达式){

执行代码;

}else {

执行代码;

}

3)执行步骤

a.先计算if后面的条件表达式的真假

a.1如果为真,就执行if块中的代码,执行完毕之后,结束if-else结构,往下执行,不会执行else中的代码。

a.2如果为假,就执行else中的代码,不会执行if中的代码,执行完毕之后,继续往下执行

4)什么时候用if-else结构呢?

如果你有两段代码,1段代码在某个条件成立的时候执行,另外1段代码在这个条件不成立的时候执行,那么这个时候就可以使用if-else结构了

用法:

if(条件)

{

条件满足的时候执行的代码;

}

else {

条件不满足的时候执行的代码;

}

  1. int num = 15;
  2. if (num % 5 == 0) {
  3. printf("哈哈,果然是5的倍数");
  4. }else {
  5. printf("嘿嘿,不是5的倍数");
  6. }

3.课堂案例

1)请客吃饭

输入李凯钱包里的钱,有100,放学请吃饭,否则,放学天台见

  1. int money;
  2. printf("请输入李凯钱包的钱数:");
  3. scanf("%d",&money);
  4. if (money>100) {
  5. printf("放学请吃饭\n");
  6. }else {
  7. printf("放学天台见\n");
  8. }
  9. printf("回家了\n");

4.课堂练习 

请用户输入他儿子的语文、数学和英语成绩,只有有1门不及格,就打死,否则吻一个

  1. int chineseGrade = 0;
  2. int mathGrade = 0;
  3. int englishGrade = 0;
  4. printf("请输入语文、数学、英语成绩:");
  5. scanf("%d%d%d",&chineseGrade,&mathGrade,&englishGrade);
  6. if (chineseGrade<60 || mathGrade<60 || englishGrade<60) {
  7. printf("打死\n");
  8. }else {
  9. printf("吻一个\n");
  10. }

5.IF-ELSE结构使用注意

1)代码缩进

2)if后的表达式任意 ,结果为0就是假,非0就是真

3)分号不要乱写

4)if与else是1个整体,所以你不要在中间写代码

5)if和else中的代码绝对不会同时执行,因为if后的条件表达式的结果只有两个,不会有3个。成立就执行if,不成立就执行else,没有第3个

if和else无论如何必须只会执行其中1个

6)块中的代码可以任意写

6.课堂练习

请用户输入自己的年龄和性别,1代表男,0代表女,如果性别是女的,就直接显示,你妈妈喊你回家吃饭,否则,再判断是否成年,如果成年,就显示,给你点刺激的东西看,否则,你妈妈喊你回家吃饭

  1. int sex = 0;
  2. int age = 0;
  3. printf("请输入年龄和性别,1代表男,0代表女:");
  4. scanf("%d%d",&sex,&age);
  5. if (sex == 0) {
  6. printf("你妈妈喊你回家吃饭\n");
  7. }else {
  8. if (age >= 18) {
  9. printf("给你点刺激的东西看\n");
  10. }else {
  11. printf("你妈妈喊你回家吃饭\n");
  12. }
  13. }

十五、IF - ELSE IF结构

1.IF结构的缺点:

if的缺点:只能在满足条件的时候执行1段代码,条件不满足的时候搞不定

if-else的缺点:只能判断1个条件

新的需求:

需要按照顺序判断多个条件,只要有1个成立,就要执行对应的事情,后面的就不需要再判断了。只有前面的条件不成立的时候,才判断后面的条件

2.IF -ELSE IF - ELSE IF -ELSE结构

1)作用:依次按照顺序判断多个条件,只要有1个成立,就执行对应的代码

2)语法:

if(表达式1)

{

执行代码;

}else if (条件表达式2){

执行代码;

}else if(条件表达式3) {

执行代码; 

}else {

执行代码;

}

3)执行步骤:

从上到下的依次的判断每1个条件表达式的结果,只要有1个条件表达式成立,就执行对应的代码。只有当前面的条件表达式不成立的时候才会继续向下判断。如果所有的条件表达式都不成立,那么就执行else中的代码

试例:

  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. int money = 0;
  4. printf("请输入李凯的钱包的钱数:");
  5. scanf("%d",&money);
  6. if (money > 500) {
  7. printf("走,我们去吃大餐\n");
  8. }else if (money>=400) {
  9. printf("走,我们去吃中餐\n");
  10. }else if (money >= 300) {
  11. printf("走,我们去吃小餐\n");
  12. }else if (money >= 200) {
  13. printf("走,我们去吃小餐\n");
  14. }else if (money >= 100) {
  15. printf("走,我们去喝1杯水");
  16. }else {
  17. printf("放学别走,打得你满地找牙");
  18. }
  19. return 0;
  20. }

3.注意事项

1)只要前面的条件成立,就会执行对应的代码,执行完毕之后,立即结束这个结构,哪怕后面的条件是成立的,也不会执行

2)如果判断了后面的条件,那么就说明前面的条件是不成立的,所以后面的条件中不需要特别声明,写前面的条件的不成立

3)判断的顺序最好不要乱,如果你写乱了,那么条件就要完整一些。但是一般情况下,我们从上到下的条件的范围是逐步缩小的

4)else可以省略,如果省略,那么就没有默认执行代码了

5)else if的个数可以任意,根据你的具体情况而来,有多少个条件判断就可以写多少个。

4.案例演示

成绩自动奖励系统,请输入土豪儿子的成绩,90奖励一台保时捷汽车,送电池,80奖励一台奔驰汽车,带遥控器,70奖励一台大众汽车,60奖励一台拖拉机,30打屁股,30以下,断绝父子关系。

  1. printf("请输入儿子的成绩:");
  2. int grade = 0;
  3. scanf("%d",&grade);
  4. if (grade >= 90) {
  5. printf("奖励1台保时捷汽车\n");
  6. }else if (grade >= 80) {
  7. printf("奖励1台奔驰汽车\n");
  8. }else if (grade >= 70) {
  9. printf("奖励一台大众汽车\n");
  10. }else if (grade >= 60) {
  11. printf("奖励1台拖拉机\n");
  12. }else if (grade >= 30) {
  13. printf("大屁股\n");
  14. }else {
  15. printf("断绝父子关系");
  16. }


​​​​​​​

十六、三元表达式

1.到目前为止,我们学习了很多很多的运算符

根据参与的操作数的个数将运算符分为如下几类

双目运算符/双元运算符:指的就是参与这个运算的操作数有2个

+

-

*

/

%

>

<

.......

单目运算符/一元运算符:只有1个数据参与运算

自增自减

三元运算符/三目运算符:指的是有3个数据参与运算的运算符

2.三元表达式

语法:

条件表达式 ? 值1 :值2;

  1. int i = 5;
  2. i>0 ? 100 :200;

三元表达式的结果,如果条件表达式成立,那么这个三元表达式的结果就是值1,否则就是值2

  1. int i = 5;
  2. int num = i>0 ? 100 :200;
  3. printf("num的值是%d\n",num);

因为成立不成立都是int类型,所以可以用int类型来接收

3.三元表达式可以部分代替if-else

要得到1个数据,这个数据的大小是根据某个条件来的,可以使用if-else,也可以是三元表达式

但是这个使用三元表达式更简洁一些

  1. int i = 5;
  2. if (i > 0) {
  3. printf("num的值是%d\n",100);
  4. }else {
  5. printf("num的值是%d\n",200);
  6. }

不仅可以弄数字,还可以进行输出打印,也可以赋值,赋值的话是后面的字节数

    int i = 5;

    int j = i > 10 ? printf("哈哈哈\n") :printf("呵呵\n");

    printf("j的值是:%d\n",j);

一个中文占用3个字节,\n字符占用一个字节 

4.注意

1)条件表达式其实可以是任意的表达式,反正0为假,非0为真

2)值1和值2可以是1个表达式,反正都是各自的结果

十七、如何产生1个随机数

1.先引入1个系统头文件

#include <stdlib.h>

2.调用arc4random_uniform()函数

在其中传入1个整数n,就会返回0 -> (n-1)的随机数

int num = arc4random_uniform(10);

就会产生0-9的随机数,并赋值给num

3.如何产生1个指定范围的随机数

  1. //产生145-90的随机数
  2. int num1 = arc4random_uniform(46)+45;

int num1 = arc4random_uniform(最大数-最小数+1)+最小数;

十八、第四天作业

1.请写1个ATM程序

定义1个变量,用来存储该ATM机器中剩余的金额

接收用户输入取款金额,由于ATM机器只支持100的票子,如果用户输入的取款金额不是100的倍数的话,则打印“对不起,本机器无法提供输入的面额"

如果用户输入的取款金额大于了ATM的剩余金额,则打印“对不起,余额不足"

如果用户输入的取款金额是100的倍数,并且小于等于ATM的剩余金额就打印“正在出钞,请从出钞口拿钱,ATM机器剩余XX元"

  1. printf("-------欢迎使用ATM取款机程序--------\n");
  2. int price = 0;
  3. int surplus = 521;
  4. printf("请输入取款金额:");
  5. scanf("%d",&price);
  6. if (price %100 != 0) {
  7. printf("对不起,本机器无法提供输入的面额\n");
  8. }else if (price > surplus) {
  9. printf("对不起,余额不足\n");
  10. }else if (price % 100 == 0) {
  11. int cha = surplus - price;
  12. printf("正在出钞,请从出钞口拿钱,ATM机器剩余%d元\n",cha);
  13. }

2.请用户从控制台输入3个整型的数,显示这3个数中的最大的数

  1. int num1 = 0;
  2. int num2 = 0;
  3. int num3 = 0;
  4. printf("请输入3个数:");
  5. scanf("%d%d%d",&num1,&num2,&num3);
  6. if (num1 > num2 && num1 > num3) {
  7. printf("num1的值最大,为%d\n",num1);
  8. }
  9. if (num2 > num1 && num2 > num3) {
  10. printf("num2的值最大,为%d\n",num2);
  11. }
  12. if (num3 > num1 &&num3 > num2) {
  13. printf("num3的值最大,为%d\n",num3);
  14. }
  1. //方式二:
  2. if (num1 > num2) {
  3. if (num1 > num3) {
  4. printf("num1的值最大,为%d\n",num1);
  5. }else {
  6. printf("num3的值最大,为%d\n",num3);
  7. }
  8. }else {
  9. if (num2 > num3) {
  10. printf("num2的值最大,为%d\n",num2);
  11. }else {
  12. printf("num3的值最大,为%d\n",num3);
  13. }
  14. }
  1. //方式三
  2. int senMax = num1>num2 ? num1 :num2;
  3. int bigMax = senMax > num3 ? senMax :num3;
  4. printf("最大的值为:%d",bigMax);

 3.乔布斯买了1框鸡蛋(10个),如果这其中的鸡蛋少于5个,他就吃掉,否则就退货,坏蛋的数随机产生,产生1个0-10的随机数,判断这个随机数的大小

  1. int badEgg = arc4random_uniform(11);
  2. printf("badEgg的值是%d\n",badEgg);
  3. if (badEgg < 5) {
  4. printf("吃掉这些鸡蛋\n");
  5. }else {
  6. printf("坏蛋太多,进行退货\n");
  7. }

4.要求用户输入两个整型的数a、b,如果a能够被b整除或者a加b的结果大于100,则输出a的值,否则输出b的值 

  1. int a = 0;
  2. int b = 0;
  3. printf("请输入两个整数:");
  4. scanf("%d%d",&a,&b);
  5. if ((a % b == 0) || (a + b > 100)) {
  6. printf("a的值是%d\n",a);
  7. }else {
  8. printf("b的值是%d\n",b);
  9. }

5.请用户输入1个实数(浮点型),请编写算法对这个实数进行四舍五入到个位的运算

  例如:12.56经过四舍五入运算得到13,而12.45经过四舍五入运算得到12

  1. printf("输入1个实数:");
  2. float num = 0.0f;
  3. scanf("%f",&num);
  4. //拿到小数的整数部分;
  5. int i = num;
  6. float xiaoshu = num - i;
  7. if (xiaoshu >= 0.5) {
  8. i++;
  9. }
  10. printf("num经过四舍五入之后为%d\n",i);

6.请用户输入密码,如果密码是888888则提示正确,否则要求再输入1次,如果第2次输入正确,就提示正确,否则就提示错误

  1. int passward = 0;
  2. printf("请输入密码:");
  3. scanf("%d",&passward);
  4. if (passward == 888888) {
  5. printf("密码正确\n");
  6. }else {
  7. printf("请重新输入密码:");
  8. scanf("%d",&passward);
  9. if (passward == 888888) {
  10. printf("密码正确\n");
  11. }else {
  12. printf("密码错误\n");
  13. }
  14. }

7.请用户输入年龄,如果大于等于18岁,则告知用户本网站可以查看,如果小于10岁,则告知用户不允许查看,如果大于等于10岁并且小于18岁,则再次询问用户是否要继续查看,如果用户的选择是继续查看,就显示给他看,否则就提示“你放弃查看”

提示:在询问用户是否需要查看的时候,可以提示用户输入y或者n的字符,y代表yes,n代表no

请问你要继续查看吗 y/n

接收用户输入y或者是n

  1. printf("请输入年龄:");
  2. int age = 0;
  3. scanf("%d",&age);
  4. if (age >= 18) {
  5. printf("本网站可以查看");
  6. }else if (age < 10) {
  7. printf("不允许查看");
  8. }else {
  9. printf("是否要继续查看,请输入y或者n,y代表yes,n代表no\n");
  10. rewind(stdin);
  11. char suggest = 'a';
  12. scanf("%c",&suggest);
  13. if (suggest == 'y') {
  14. printf("显示查看内容\n");
  15. }else {
  16. printf("你放弃查看\n");
  17. }
  18. }

8.编写1个程序,请用户输入1个整数,如果输入的整数大于0,则为该数加上100,如果小于0,则加上500,如果等于0,就什么都不做。最后打印这个数据的值 

  1. printf("输入1个整数:");
  2. int num = 0;
  3. scanf("%d",&num);
  4. if (num >0) {
  5. num += 100;
  6. }else if (num < 0) {
  7. num += 500;
  8. }
  9. printf("num的值是%d\n",num);

9.编写1个程序,请用户输入1个字符数据,如果这个字符是1个小写的字母,则输出“你输入的是1个小写的字母",如果这个字符是1个大写的字母,则输出"你输入的是1个大写的字母",否则,则提示你输入的不是字母

  1. char num = 'a';
  2. printf("请输入1个字符:");
  3. scanf("%c",&num);
  4. if (num >= 97 && num <= 122) {
  5. printf("你输入的是1个小写的字母\n");
  6. }else if (num >= 65 && num <=90) {
  7. printf("你输入的是1个大写的字母\n");
  8. }else {
  9. printf("你输入的不是字母\n");
  10. }

10.请编写如下程序:请用户输入星期数(1-7),打印对应的英文星期天,如果用户输入的不是1-7的数,就提示:“你是从火星来的吗?" 

  1. printf("请输入星期数:");
  2. int week = 0;
  3. scanf("%d",&week);
  4. if (week == 1) {
  5. printf("Monday\n");
  6. }else if (week == 2) {
  7. printf("Tuesday\n");
  8. }else if (week == 3) {
  9. printf("Wednesday\n");
  10. }else if (week == 4) {
  11. printf("Thursday\n");
  12. }else if (week == 5) {
  13. printf("Friday\n");
  14. }else if (week == 6) {
  15. printf("Saturday\n");
  16. }else if (week == 7) {
  17. printf("Sunday\n");
  18. }else {
  19. printf("你是从火星来的吗\n");
  20. }

11.请用户输入1个年份,判断用户输入的年份是否为闰年,如果是闰年就显示“是闰年”,否则就提示不是闰年",满足一下两个条件之一的年份就是闰年 1)年份能够被400整除 2)年份能够被4整除但是不能够被100整除

  1. int year = 0;
  2. printf("请输入1个年份:");
  3. scanf("%d",&year);
  4. if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)) {
  5. printf("输入的是闰年\n");
  6. }else {
  7. printf("输入的不是闰年\n");
  8. }

12.请用户输入1个年份,再输入1个月份,显示这1年的这1月有多少天

提示:1,3,5,7,8,10,12 月份,无论是哪个月份,都有31天 。4,6,9,11月份,无论是哪个年份,都是30天 ,如果是2月份,年份是闰年的话那么就有29天,否则就是28天

  1. printf("请输入年份和月份:");
  2. int year = 0;
  3. int month = 0;
  4. scanf("%d",&year);
  5. scanf("%d",&month);
  6. if (month == 2) {
  7. if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)) {
  8. printf("%d年是闰年,2月有29天\n",year);
  9. }else {
  10. printf("%d年不是闰年,2月有28天\n",year);
  11. }
  12. }
  13. if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
  14. printf("%d月有31天\n",month);
  15. }
  16. if (month == 4 || month == 6 || month == 9 || month == 11) {
  17. printf("%d月有30天\n",month);
  18. }

 13.编写程序从键盘输入两个整数,检查第一个数能否被第二个数整除,如果可以就打印yes,否则就打印no

  1. int num1 = 0;
  2. int num2 = 0;
  3. printf("请输入两个整数:");
  4. scanf("%d%d",&num1,&num2);
  5. if (num2 % num1 == 0) {
  6. printf("yes\n");
  7. }else {
  8. printf("no\n");
  9. }

 14.某邮局对邮寄包裹有如下规定,若包裹的长宽高任意1尺寸超过1米或者重量超过30千克,则不予邮寄,对可以邮寄的包裹每件收手续费10元,请编写程序接收用户输入包裹的长宽高和重量,判断是否满足符合邮寄的条件,如果满足则根据下表的收费标准显示邮资

  1. //长度
  2. double length = 0;
  3. //宽度
  4. double width = 0;
  5. //高度
  6. double height = 0;
  7. //重量
  8. double weight = 0;
  9. //价钱
  10. double price = 0;
  11. printf("请输入包裹的长宽高和重量,注意长宽高单位为米,重量为千克:");
  12. scanf("%lf%lf%lf%lf",&length,&width,&height,&weight);
  13. if (length > 1 || width > 1 || height > 1 || weight > 30) {
  14. printf("物品不能够邮寄");
  15. }else {
  16. if (weight <= 10) {
  17. price = weight * 15;
  18. }else if (weight > 10 && weight <= 20) {
  19. price = weight * 10;
  20. }else if (weight > 20 && weight<= 30) {
  21. price = weight * 8;
  22. }
  23. }
  24. if (price != 0) {
  25. printf("邮寄的价格为:%.2lf\n",price);
  26. }

第五天 循环控制

十九、复习 

1.逻辑运算符和逻辑表达式

2.IF结构

作用:可以将1段代码在满足条件的时候执行,否则就算了

3.变量的作用域

声明在1个作用域中的变量只能在这个作用域中访问,出了这个作用域就无法访问了

4.IF-ELSE结构

作用:可以在条件满足的时候执行1段代码,在条件不满足的时候执行另外1段代码

5.IF -  ELSE IF 结构

作用:可以按照顺序判断多个条件,并且执行条件满足的对应的代码

6.三元表达式

1)语法:

表达式0?值1 (表达式):值2(表达式)

2)结果

如果第0个表达式成立,那么这个3元表达式的结果就是表达式1.否则就是表达式2的结果

int num = 10;

int res = num > 0 ?100 :200;

int res = num - num ? num-num : num*num;

3)作用:

可以根据条件来选择两个数据中的1个,当你有两个数据需要根据条件来进行选择的时候,那么这个时候可以使用三元表达式

7.产生随机数

8.算术运算符的优先级和结合性

第七天 函数

1.goto跳转语句

1)goto去哪 C语言的作用:可以将CPU的执行跳转到当前函数的别的地方继续执行

2)如何使用

a.先为想要跳转到的地方做1个标记,为这个地方取一个标签名,标签名随意,要符合标识符的命名规则和规范

b.想要在什么地方跳转到做标记的地方,就在哪个地方写上标签名

goto 标签名;

  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. // printf("Hello, World!\n");
  4. loop:
  5. printf("hahha\n");
  6. printf("heiheihei\n");
  7. printf("wawawa\n");
  8. goto loop;
  9. return 0;
  10. }

3)执行原理

当执行到goto语句的时候,CPU就会跳转到指定的标签的地方继续往下执行

  1. int main(int argc, const char * argv[]) {
  2. int username = 0, password = 0;
  3. loop:
  4. printf("用户名:");
  5. scanf("%d",&username);
  6. printf("密码:");
  7. scanf("%d",&password);
  8. if (username != 123456 || password != 88888) {
  9. goto loop;
  10. }
  11. return 0;
  12. }

4)goto语句也可以实现我们的循环操作,但是在使用的时候,要注意不要造成死循环

不建议大家经常使用goto ,因为它不安全,容易造成死循环,除非你特别确定的情况下不会有死循环,那么就可以使用goto。

5)注意

a.标签名随意,但是要符合标识符的命名规则及规范

标识符:那就是程序员可以自己给名字的东西,统称为标识符

                变量名、函数名、标签名

b.goto可以往前跳,也可以往后跳

c.只能在当前函数中跳

d.取标签名下面的代码不能是声明变量


​​​​​​​

2.函数的基本使用

1)循环:同1段代码反复不停的执行

      重用/复用:有1段代码,这个地方需要执行1次,那个地方需要执行1次,多个地方都需要执行  1次,但是不是反复不停的执行。

2)代码重用的解决之道

a.最简单、最粗暴的方式就是使用复制粘贴

缺点:

----->. 技术含量低,容易被歧视

----->  代码冗余,同样的代码出现太多次了

-----> 后期的维护和修改非常不方便

b.使用函数来实现代码的重用

-----> 首先将那段需要被重用的代码塞到函数中,只要塞1次

-----> 什么时候想要执行这个函数中的代码,我就只需要喊1声这个函数就可以了

-----> 只需要塞1次,可以多次呼唤

3)函数的定义

a.位置 应该定义在main函数的外面,也就是直接写在源文件下

b.语法

返回值类型 函数名称([参数列表]). //函数头

{

   写上那段需要被重用的代码;  //函数体

}

  1. void showLogo() {
  2. printf("===================================");
  3. printf("= =");
  4. printf("= 超好玩的推箱子游戏 =");
  5. printf("= =");
  6. printf("===================================");
  7. }

注意上面的写法,可能会报这个警告

 需要注意的是即使没有参数的时候,也要加一个void来消除这种警告

  1. void showLogo(void) {
  2. printf("===================================\n");
  3. printf("= =\n");
  4. printf("= 超好玩的推箱子游戏 =\n");
  5. printf("= =\n");
  6. printf("===================================\n");
  7. }

4)函数的调用

a.当我们将代码交给CPU去执行的时候,CPU只会执行main函数中的代码,别的地方的代码不会自动执行

b.如果我们想要去执行自定义函数中的代码,很简单,只需要在想要执行的地方调用这个函数就可以了

调用函数的语法:

函数名();

c.当CPU执行的代码,是在调用函数的时候,这个时候,CPU会跳转到这个函数的内部去执行这个函数内部的代码,执行完毕之后,在返回来继续往下执行。

d.什么时候想要执行函数中代码,什么时候任意调

5)使用函数的注意:

a.再次强调,请找准定义函数的位置,是在main函数的外面

b.目前为止,返回值的类型直接写void,不要关心void是什么意思,再讲返回值之前,这个地方直接写void

c.函数名称

命名要符合标识符的命名规则和规范

函数的命名规则规范:

函数的名称一定要取得有意义,别人一看你函数的名字,就知道函数中的代码在做什么事情

d.写在大括弧里面的叫做函数体

e.小括弧的后面没有分号

f.定义一个函数,如果这个函数不被调用那么这个函数中的代码永远不会执行

g.如果想要执行自定义函数的代码,那么就要调用这个函数。

函数名();

调用函数的时候,小括弧也是必不可少的

3.全局变量和局部变量

1)变量

变量:在内存中用来存储数据开辟的那块空间

局部变量:定义在函数内部的变量,我们就叫做局部变量,所以我们之前学习的变量都是局部变量

  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. //局部变量
  4. int num = 0;
  5. return 0;
  6. }

全局变量:定义在函数外面的变量,我们就叫做全局变量,直接定义在源文件的下面

  1. #include <stdio.h>
  2. //全局变量
  3. int num1 = 1;

2)全局变量和局部变量的异同点

相同点:都是变量,都是在内存中开辟一块空间来存储数据

不同点:

a.声明的位置不同,局部变量声明在函数的内部,而全局变量声明在函数的外部

b.作用域不同

局部变量,只能在当前函数的内部访问

全局变量,从定义这个全局变量的地方开始,后面的所有的函数中都可以访问这个全局变量,一般情况下,全局变量都是定义在最顶上的,#include下面

c.默认值不同

局部变量,声明1个局部变量,如果没有为这个局部变量赋初始值,那么这个局部变量中有值,值是1个垃圾值,是个随机数

全局变量,声明1个全局变量,如果没有为这个全局变量赋初始值,那么这个全局变量的初始值默认就是0,会自动初始化为0,如果全局变量的类型是char类型,并且我们也没有初始化,那么系统就会自动的为这个char变量赋值1个'\0','\0‘代表1个不可见的字符,这个字符的ASCII码就是0 

  1. #include <stdio.h>
  2. //全局变量
  3. int sum;
  4. double d1;
  5. float f1;
  6. char ch;
  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. //局部变量
  4. int num;
  5. printf(" num的值是:%d\n sum的值是:%d\n d1的值是%lf\n f1的值是%f\n ch的值是%c\n",num,sum,d1,f1,ch);
  6. return 0;
  7. }

d.创建和回收的时间不同 

局部变量,CPU执行声明局部变量的那句话的时候,才会在内存中声明变量,当作用域结束了以后,就会自动回收

全局变量,程序一旦启动,就在内存中创建全局变量,程序结束的时候,全局变量就会被回收,如果我们创建的是Command Line Tool,那么一运行之后,执行完程序就结束了,不像创建app那样,会一直运行。

局部变量演示:

  1. void test (void) {
  2. int age = 10;
  3. age ++;
  4. printf("age的值是%d\n",age);
  5. }
  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. test();
  4. test();
  5. test();
  6. return 0;
  7. }

打印一下结果,发现age的值始终都是11

因为执行完一次test函数,age就会被回收,等到再次调用test函数的时候,会重新创建age

 全局变量演示:

  1. #include <stdio.h>
  2. //全局变量
  3. int sum = 10;
  1. void test1 (void) {
  2. sum ++;
  3. }
  4. void test2 (void) {
  5. sum ++;
  6. }
  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. test1();
  4. test2();
  5. printf("sum的值是%d\n",sum);
  6. return 0;
  7. }

打印结果发现,sum的值为12,每一次对sum值的使用,都是对同一个变量进行使用

如果定义了一个局部变量和全局变量的命名相同,那么在这个局部变量所对应的这个函数中,使用的是局部变量的值

  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. int sum = 0;
  4. test1();
  5. test2();
  6. printf("sum的值是%d\n",sum);
  7. return 0;
  8. }

此时sum的值是0

 3)全局变量的特点

a.程序一启动就会创建,直到程序结束的时候才会被回收

局部变量,每次调用函数都会重新声明1个,函数结束的时候局部变量就回收

无论在哪个函数中,访问全局变量,访问的都是同1个,具备共享性,将数据声明为全局变量,那么所有函数都能访问,都可以共享这个数据

b.什么时候我们需要把1个数据定义为全局变量呢?

如果这个数据想要被多个的函数访问,那么就可以将这个数据定义为全局变量

第八天 进制

1.预处理指令 /预处理代码

C语言中的代码主要分为2类

1)  C代码:我们之前学的代码都叫做C代码

2)预处理代码 :以#开头的代码叫做预处理代码

2.手写一个C程序的步骤

1)在.c源文件中写上符合C语言规范的源代码

2)编译,使用cc -c 指令将C语言的源代码编译成.o的目标文件

a. 先检查源文件中的代码是否符合语法规范

YES:生成目标文件

NO:报错

3)链接:使用cc 指令将目标文件链接成1个可执行文件

   a.为目标文件添加启动代码

4)执行可执行文件

                      ------------>  这都是编译器在编译的时候做的事情<-------------

c.源文件 ---> 执行.c文件中的预处理代码 --->检查语法 ----> 编译成.o目标文件 -->链接生成可执行文件---> 执行

3.预处理指令

1)预处理指令的分类:

a.文件包含指令:#include

b.宏定义 :   #define

c. 条件编译指令:  #if

2)预处理指令的特点:

a.都是以#开头

b.预处理指令的后面没有分号

c.在编译的时候,检查语法之前

4.文件包含指令 #include

1)作用:可以将指定的文件的内容拷贝到写指令的地方

2)语法:

#include "文件路径"

#include <文件路径>

3) 用法

我们可以在桌面上建立一个文件

然后在打开文件,编写代码,这里编写两个输出语句

 

 在程序中使用#include指令

  1. #include <stdio.h>
  2. int main(int argc, const char * argv[]) {
  3. // insert code here...
  4. #include "/Users/lvlvjing/Desktop/1.txt"
  5. return 0;
  6. }

运行程序,就可以在终端看到文件中的代码

#include "/Users/lvlvjing/Desktop/1.txt"

在编译之前,就会执行预处理代码,这个时候,#include指令的作用,将后面这个文件的内容拷贝到这个地方,然后在检查语法,生成.o目标文件

就相当于写指令的地方实际上写的文件中的内容

4)使用注意

a.就是被包含的文件的代码必须要符合C语法规范,否则在编译检查语法的时候就会报错

把文件的内容拷贝到写指令的地方要整体上没有语法错误才行

b.如果被包含的文件不存在,也会报错

5.相对路径和绝对路径

1)一般情况下,被包含的文件我们不会乱放,而是放在源文件目录下,和当前的.c在同一个目录

这样做的好处在于方便管理

如果我们将被包含的文件放在和源文件同1个目录下,写这个文件的路径的时候就不要写全路径了,而是写1个相对路径,和当前源文件的路径相同的部分删除掉就可以了

  1. #include <stdio.h>
  2. int main(int argc, const char * argv[]) {
  3. // insert code here...
  4. #include "1.txt"
  5. return 0;
  6. }

2)绝对路径和相对路径

绝对路径:路径从根目录开始/

                  /Users/lvlvjing/Desktop/cc.c

相对路径:相对于当前这个文件夹的路径

                   和当前文件路径相同的部分删掉,这样的路径就叫做相对路径,相对路径中没有/

如果路径是1个相对路径,就会从当前文件夹所在的目录中去找 

6.文件路径使用双引号和尖括号的异同点

1)都是将指定的文件的内容包含到写指令的地方

2)不同点在于寻找指定文件的方式不一样

a.如果文件路径是使用双引号引起来的

#include "1.txt"

 --->先去当前源文件所在的目录中查找这个文件,如果有,直接包含,如果没有,就去系统自带的编译器目录中查找,如果有就直接包含,如果没有就报错

系统自带的编译器目录在哪?

/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/user/include

b.如果文件路径我们使用尖括号引起来

#include<1.txt>

--->直接就去编译器目录当中查找,如果有包含,没有就报错

7.系统自带的编译器目录

C语言中有1个标准的函数库,--->系统自带了很多的函数,苹果已经把我们开发中最常用的那些功能写成了函数,集成在了C语言中,我们如果要使用这些功能,那么我们直接调用这些函数就可以了。

函数分为声明和实现。苹果将功能类似的那些函数的声明放在了1个.h的头文件中,再将所有的头文件放在系统的编译器目录中。

stdio.h 这个头文件中存储的函数的声明都是和输入输出相关的函数

stdlib.h 这个头文件存储的是一些核心函数

如果我们要调用C语言函数库的函数,首先应该先声明这个函数,在调用这个函数

但是每声明1次,就太麻烦了,所以,我们直接将这个函数声明所在的头文件包含进来。

总结:

如果我们要调用C语言函数库中的函数

1)先将这个函数的声明所在的头文件包含进来

2)才可以调用这个函数

使用建议:

我们自定义的被包含文件,不要乱放,放在和源文件同一个目录下

如果包含的文件是我们自定义的文件,那么使用""

如果包含的文件是系统自带的文件,那么使用<>

8.多文件开发

1)1个C程序其实就是由1个1个函数组成的,当我们的程序很庞大的时候,那么程序中的函数就会有很多,如果将所有的函数都放在main.c文件中就会混乱,不利于开发

2)每1个人负责1个模块的开发,程序其实都是由1个1个的模块组成的

不同的人负责不同的功能开发。模块/功能,谁要使用这个功能,只要直接调用就可以了

3)如何分模块开发

写1个程序,有1个模块:做数学运算

创建一个新的C语言类,需要选择C File

a.先创建1个.c文件,将这个功能模块的相关函数写在这个.c文件中,调用者这个时候,如果想要调用模块中的函数的话,就得自己先声明这些函数

创建好之后将下面的叉去掉

 b.这样遇到的问题

如果写模块的人新建了一个函数,调用者想要调用的话,就还得自己再声明

如果写模块的人删除了一个函数,那么调用者也要删除函数的声明

如果写模块的人修改了函数头,那么调用者也要跟着修改

c.函数的声明和实现,其实都应该叫写模块的人来做,因为这些东西只有写模块的人自己清楚

所以,我们模块开发的时候,做法是这样的,写模块的人提供两个文件

.h文件 head头文件 这个文件专门写函数的声明

.c文件 专门写函数的实现

.h文件创建Head File文件

d.如果有人想要使用这个模块中 的函数,只需要把这个头文件包含进来就可以了

这个时候 .h文件和.c实现文件都是由写模块的人来写,如果写模块的人要新增函数,修改函数,需要将.h文件的声明一起改

调用的人只需要包含这个.h头文件

4)总结

当我们的程序的函数过多的时候,就要考虑分模块开发。

如何分模块开发:

1个模块至少包含两个文件

.h文件 专门写函数的声明

.m文件 专门写函数的实现

谁要调用这个模块中的函数,只要包含这个模块的头文件就可以了

创建C文件的时候点击下面的包含头文件,就可以同时创建一个.h文件

9.进制

1)进制

一种计数的方式,侧重点在于计数的时候是逢多少进一

十进制:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20

2)C语言可以识别的进制有哪些呢?

无论是哪一种进制,都是用来计数的,唯一不同的是在于逢多少进一

a.二进制

逢二进一,每1位数字使用0或者1来表示

0 1 10 11 100 101 110 111 1000

在C语言中如何写1个二进制呢?

在C语言中,如果要写1个二进制,那么就在这个二进制的前面加一个0b的前缀

  1. int num = 0b1000;
  2. printf("num的值是%d\n",num);

b.八进制

逢八进一 每位数字只能是0,1,2,3,4,5,6,7

C语言中 如何写1个八进制数呢?

在八进制数的前面加一个0,代表这个数就是8进制数

  1. //八进制数
  2. int num1 = 0126;
  3. printf("num1的值是:%d\n",num1);

%d 将整型变量中的数据以十进制的形式输出来

%o将整型变量的数据以八进制的形式输出来

  1. //八进制数
  2. int num1 = 0126;
  3. printf("num1的值是:%o\n",num1);

没有以二进制输出的占位符

c.十进制

逢十进一 每一位 0,1,2,3,4,5,6,7,8,9

在C语言中直接写1个整数,那么默认就是10进制

%d 将整型变量中的数据以十进制的形式输出来

d.十六进制

逢十六进一 每一个 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,10

167ade

在C语言中如何写一个十六进制数,加一个0x前缀

以%x占位符输出十六进制数据

  1. //十六进制数
  2. int num2 = 0x12af;
  3. printf("num2的值是:%x\n",num2);


​​​​​​​

0x123acf 下一个数是0x123ad0

10.重点掌握

1)每一种进制的数据的每一位是什么

2)如何在C代码中写每个进制的数据

3)占位符:

%o   %d  %x

4)一定要熟练使用各个进制来数数

二进制      八进制      十进制    十六进制

0                  0                 0             0

1                  1                   1             1

10                2                  2             2

11                3                  3            3

100             4                  4            4

101              5                  5            5

110             6                  6            6

111               7                 7            7

1000           10               8            8

1001           11                9            9

1010           12               10           a

1011           13               11              b

1100           14               12           c

1101            15                13           d

1110           16                 14           e

1111            17                 15          f

10000.       20.               16.         10

必须要会数数

11.进制之间的转换

1)基本概念

10011001

数码:指的是这个数据的每一位数字

数位:数码在这个数中的位置,从右到左,依次递增,从0开始

比如:1890123  3这个数码是3,数位是0

基数:就是每一个数码可以用多少个数据来表示,基数其实指的是这个数的进制

位权:数码*(基数的数位次方) 任何数的0次方都等于1

2)十进制与二进制之间的转换

a.十进制转换为二进制(除2取余法)

除2取余法:将这个数除以2,直到商为0,然后将余数倒序,就是这个十进制对应的二进制

178除以2 = 89 ...0

89除以2 =44...1

44除以2 = 22...0

22除以2 =11...0

11除以2 = 5 ...1

5除以2 = 2 ...1

2除以2 = 1...0

1除以2 = 0..1

所以178对应的二进制是10110010

b.十进制转换为二进制

加权法:将这个二进制数的每个数码的位权相加,就是这个二进制对应的十进制

10101 

1*2的0次方+1*2的2次方+1*2的4次方 = 21

二进制的位权

0             1

1             2  

2            4

3            8

4           16

5           32

6           64

3)十进制与八进制之间的转换

a.十进制转换为八进制

除八取余  这个数除以8,直到商为0为止,然后将余数倒序,就是这个十进制的八进制

78 除以8 = 9 余数为6

9除以8 = 1 余数为1

1除以8 = 1 余数为1

所以78对应的八进制为116

b.八进制转换为十进制

加权法 :将八进制的每一个数码的位权相加,得到的就是对应的十进制数

0342 

2*8的0次方+4*8的一次方+3乘以8的二次方 = 226

12.原码、反码、补码

1)无论任何数据,在内存中存储数据的时候都是以二进制的形式存储的

int num = 10;

原码、反码、补码都是二进制,只不过二进制的不同的表现形式

数据是以补码的二进制存储的

2)1个int类型的变量,在内存中占据4个字节,32位。

00000000 00000000 00000000 00000000

在不考虑正负的情况下,1个int类型的变量可以表示接近43e种数据 2的32次方

为了可以表示正负性,使用最高位来表示这个数的正负性

如果最高位是0,那么表示这个数是一个正数

如果最高位是1,那么表示这个数是一个负数

所以来表示数据的只有31位,所以1个int类型的变量,最小值是:-2147483648 

最大值是:2147483637

3)原码:

最高位表示符号位,剩下的位数,是这个数的绝对值的二进制

10的原码:(10是正数,符号位是0,然后算10的绝对值,然后再算10的二进制)

00000000 00000000 00000000 00001010

-8的原码(负数首先是1,,然后算-8的绝对值,是8然后再算8的二进制)

10000000 00000000 00000000 00001000

正数的绝对值是自己,负数的绝对值去掉负号

-20的原码

10000000 00000000 00000000 00010100

4)反码

正数的反码就是其原码,负数的反码就是在其原码的基础之上,符号位不变,其他位取反

10的反码:

00000000 00000000 00000000 00001010

-8的反码:

11111111 11111111 11111111 11110111

5)补码

正数的补码就是其原码,负数的补码就是在其反码的基础之上加1

10的补码

00000000 00000000 00000000 00001010

-8的补码

11111111 11111111 11111111 11111000

6)任何数据都是以其二进制的补码形式存储在内存中

int num = -8

是以11111111 11111111 11111111 11111000来存储的

7)为什么数据以补码的形式来存储呢?

因为计算机中只有加法没有减法,为了更加低成本的计算出结果,所以用补码的形式来存储

3+2

3-2 这个减法运算对于计算机而言它的理解是3+(-2)

使用原码计算:

3的原码   00000000 00000000 00000000 00000011

-2的原码 10000000 00000000 00000000 00000010

--------------------------------------------------------

                 10000000 00000000 00000000 00000101  这个是1个负数明显是不对的

使用反码计算

3的反码    00000000 00000000 00000000 00000011

-2的反码   11111111      11111111      11111111      11111101

---------------------------------------------------------

                100000000 00000000 00000000 00000000 1溢出,去掉,结果为0 不对

使用补码计算

3的补码:  00000000 00000000 00000000 00000011

-2的补码:11111111       11111111      11111111      11111110

----------------------------------------------------------- 

                 100000000  00000000 00000000 00000001     最前面的1溢出,去掉 所以结果为1

因为使用补码来做运算效率是最高的

 补码的符号位为1,一定是负数,为0一定是正数

可以根据补码反推出他的原码

  1. int sum = 0b01100000000000000000000000000000;
  2. int sum1 = 0b01000000000000000000000000000000;
  3. int sum2 = sum +sum1;
  4. printf("sum的值是%d\n",sum);
  5. printf("sum1的值是%d\n",sum1);
  6. printf("sum2的值是%d\n",sum2);

两个整数相加,二进制相加符号位第一位位1,是负数,是因为超出了二进制表示的范围

 13.位运算

 1)位运算

位运算指的是1个二进制数据的每一位来参与运算,位运算的前提是这个数必须是1个二进制

注意:

a.参与位运算的二进制数据必须是补码形式

b. 位运算的结果也是二进制的补码形式

2)按位与:&

参与按位与的两个二进制数,如果都为1,那么结果就是1,只要有1个为0,那么结果就是0

3 & 2    == 2

第一步:先得到两个数的二进制补码形式

3的补码:00000000 00000000 00000000 00000011

2的补码:00000000 00000000 00000000 00000010

-----------------------------------------------------------------

                  00000000 00000000 00000000 00000010                2

  1. int num = 3 & 2;
  2. printf("num的值是:%d\n",num);

           

 -3 & 4 

-3的原码:10000000 00000000 00000000 00000011

-3的反码:111111111    11111111      11111111       11111100

-3的补码 :11111111     11111111      11111111       11111101

4的补码:  00000000 00000000 00000000 00000100

-------------------------------------------------------------

                 00000000 00000000 00000000 00000100          4

  1. int num1 = -3 & 4;
  2. printf("num1的值是:%d\n",num1);

-3 & -4

-4的原码:10000000 00000000 00000000 00000100

-4的反码:111111111 11111111 11111111 11111011

-4的补码:11111111 11111111 11111111 11111100

-3的补码:11111111 11111111 11111111 11111101

-------------------------------------------------------

                    11111111 11111111 11111111 11111100

这个数的反码:11111111 11111111 11111111 11111011

这个数的原码:10000000 00000000 00000000 00000100

所以这个数为-4,得到的结果为补码,需要还原为原码

  1. int num2 = -3 & -4;
  2. printf("num2的值是:%d\n",num2);

任何数按位与1的结果是:是这个数的最低位

xxxxxxxx        xxxxxxxx         xxxxxxxx        xxxxxxxx0

00000000   000000000     00000000    00000001

偶数的最低位一定是0,奇数的最低位一定为1

所以,如果要判断这个数是奇数还是偶数,只要用这个数按位与1就可以了,如果结果为1,那么就是奇数,如果结果为0,那么结果就是偶数

  1. int num3 = 10;
  2. if (num3 & 1 == 0) {
  3. //偶数
  4. }else {
  5. //奇数
  6. }

3)按位或 |

参与按位或的二进制数据,只要有1位是1,那么结果就为1,只有当两位都是0的时候结果才为0 

3|2

3的补码:00000000 00000000 00000000 00000011

2的补码:00000000 00000000 00000000 00000010

------------------------------------------------------------

                  00000000 00000000 00000000 00000011       3

  1. //按位或
  2. int num = 3 | 2;
  3. printf("num的值是:%d\n",num);

-3 | 4

 -3的补码:11111111 11111111 11111111 11111101

4的补码:  00000000 00000000 00000000 00000100

--------------------------------------------------------------

                    11111111 11111111 11111111 11111101

这是1个负数,反码为:11111111 11111111 11111111 11111100,

原码为:100000000 00000000 00000000 00000011       -3

  1. int num1 = -3 | 4;
  2. printf("num1的值是:%d\n",num1);

-3 | -4

 -3的补码:11111111 11111111 11111111 11111101

-4的补码: 11111111 11111111 11111111 11111100

----------------------------------------------------

                     11111111 11111111 11111111 11111101

这是1个负数,反码为:11111111 11111111 11111111 11111100

原码为:10000000 00000000 00000000 00000011       -3

  1. int num1 = -3 | -4;
  2. printf("num1的值是:%d\n",num1);


​​​​​​​

4)按位取反 :~

单目运算符,将这个二进制的每一位取反

~3

3的补码:00000000 00000000 00000000 00000011

按位取反:11111111 11111111 11111111 11111100  得到的依然是补码

还原反码:11111111 11111111 11111111 11111011

原码:10000000 00000000 00000000 00000100 -4

5)按位异或  ^

参与按位异或的二进制数据的位,如果相同则为0,不同为1.

3^2

00000000 00000000 00000000 00000011   3的补码

00000000 00000000 00000000 00000010 2的补码

----------------------------------------------------------

00000000 00000000 00000000 00000001 1

  1. int num2 = 3 ^ 2;
  2. printf("num2的值是%d\n",num2);

 交换两个变量的值可以用异或运算

int a = 3;

int b = 20;

a = a^b;

b = a^b;

a = a ^ b;

6)按位左移:<<

参与按位左移运算的二进制数据。向左移动指定的位数,低位不够补0,高位溢出则丢弃

3<<2

00000000 00000000 00000000 00000011

00000000 00000000 00000000 00001100          12

  1. int num3 = 3<<2;
  2. printf("num3的值是%d\n",num3);

注意:

a.左移运算有可能会改变其正负性

b.将1个数左移n位,相当于乘以2的n次方

7)按位右移:>>

参与按位右移的二进制数据,向右移动指定的位数,低位溢出丢弃,高位符号位

3>>2

000000000 00000000 00000000 00000011

00000000 00000000 00000000 00000000 

-16>>3

-16的原码:10000000 00000000 00000000 00010000

-16的反码:11111111 11111111 11111111 11101111

-16的补码:11111111 11111111 11111111 11110000

                      11111111 11111111 11111111 11111110

这是1个负数反码为:11111111 11111111 11111111 11111101

原码为:10000000 00000000 00000000 00000010

  1. //按位右移
  2. int num2 = -16>>3;
  3. printf("num2的值是%d\n",num2);

 注意:

a.按位右移不会改变正负性

b.1个数按位右移n位,相当于这个数除以2的n次方

14.变量存储的简单细节

1)我们之前学习了4种类型的变量

int 

float

double

char

无论是哪一种类型的变量,都是在内存中申请1块空间来存储数据

2)内存中的数据存储单元是由1个1个的二进制位组成的,每1个二进制位只能存储0或者1

如果我们使用1个二进制位来存储数据的话,这样的话,每1个二进制位只能表示2个数据,科学家们为了能够更方便的存储更多的数据,他们就把内存中的8个二进制位分为1组,叫做1个字节,作为存储数据的最小基本单位

如果你要往内存中存储数据的话,就至少要使用1个字节,也就是8个二进制位,这个时候,1个字节就可以最多表示256种数据,那么使用字节来存储数据的话,存储的数据就多了一些。

重点:

内存中的存储单元是由1个1个的二进制位组成的,每1个二进制位只能存储数据0或者1,将8个二进制位分成1组,作为存储数据的最小基本单位

单位换算:

8bit = 1个字节byte

1024字节 = 1KB

1024KB = 1MB

1024MB = 1GB

1024GB = 1TB

8G = 8 * 1024 * 1024*1024*8位

15.深入变量的细节

1)我们在申明变量的时候,并不是去开辟1块空间,而是向系统在内存中申请1指定字节数的连续的字节空间

int :4个字节

double:8个字节

float:4个字节

char:1个字节

2)变量占用的字节空间一定是连续的

内存中存储数据的最小基本单位是字节,每1个字节都有1个独一无二的内存地址,是1个十六进制的数,并且相邻的字节的地址一定是连续的

声明1个变量,就是在内存中分配连续的指定字节的空间

int :4个连续的字节

3)在为变量分配字节空间的时候,是从高地址向低地址分配的,分配连续的指定字节数的空间

4)存储在变量中的数据都是以数据的二进制补码形式存储进去的

10的补码:00000000 00000000 00000000 00001010

存储的时候,低位存储在低字节,高位存储在高字节

5)变量的地址:

变量的地址是组成这个变量的低字节的地址

使用&取地址可以取出变量的地址

&变量名,这个表达式的结果就是这个变量的地址

要打印地址使用%p占位符

  1. int num1 = 10;
  2. int num2 = 20;
  3. int num3 = 30;
  4. printf("num1的地址是:%p\n",&num1);
  5. printf("num2的地址是:%p\n",&num2);
  6. printf("num3的地址是:%p\n",&num3);

6)声明变量就会占用内存空间,实际上程序在运行的时候,并不是说只有我们的代码才会声明变量,系统字节也会在内存中申请空间存储数据。所以,这就造成了我们的变量实际在分配字节空间的时候,可能我们的变量并不是1个挨着1个,因为我们声明变量的同时我们的系统也在申请空间存储数据,但是不管怎样1个变量占用的字节一定是连续的

  1. int num1 = 10;
  2. double d1 = 12.12;
  3. char ch = 'a';
  4. int num2 = 20;
  5. //1.谁地址最高? num1的地址最高
  6. //2.谁的地址最低? num2的地址最低
  7. //3.d1的地址和num1的地址差几个字节 8个字节
  8. //4.d1ch的地址相差几个字节 1个字节
  9. //5.num2ch的地址相差几个字节 4个字节
  10. printf("num1 = %p\n num2 = %p\n num3 = %p \n num4 = %p \n",&num1,&d1,&ch,&num2);

会发现打印的结果跟我们的不一样,是因为系统也会申请空间存储数据


​​​​​​​

16.int类型的修饰符

1)一个int类型的变量在内存中占据4个字节,所以1个整型的变量中可以保存正负21e之间的整数

存在的问题:有一些大于int范围的整数就无法存储

思考:指定int类型的变量所占的字节数

2)int类型的修饰符

a.作用:指定int类型的变量在内存中占用的字节数

b.short修饰符:

在声明1个int类型的变量的时候,可以使用short来修饰

short int num = 10;

被short修饰的int类型的变量在内存中只占据2个字节,16位,最高位表示符号位,实际上表示数据的只有15位,所以,最小值是:-32768 最大值是:32767

要输出short修饰的int类型的变量的值,应该使用%hd来输出

如果要声明1个short int类型的变量,那么可以省略int,直接写short 

short num = 10;

c.long修饰符

在声明1个int类型的变量的时候,可以使用long来修饰

long int num = 10;

在32位的操作系统下,被long修饰的整型变量占据4个字节

在64位操作系统下,被long修饰的整型变量占据8个字节

  1. long int num = 10;
  2. int len = sizeof(num);
  3. printf("len = %d\n",len);


​​​​​​​

8个字节,64位

最高位表示符号位:63位表示数据

输出该类型的变量应该使用%ld占位符

声明的时候可以省略int

long num = 10;

d.long long修饰符

在声明1个int类型的变量的时候,可以使用long long来修饰, long long int num = 10;

无论在32位系统还是64位系统都是占用8个字节

输出该类型的变量的值使用%lld占位符

声明这个类型的变量的时候,int可以省略,long long num = 10;

17.sizeof运算符

计算变量、常量在当前系统上内存中占用的字节数

1)使用方法

a.sizeof(数据类型) 就会返回这个数据类型的变量在内存中占据多少字节

  1. int len = sizeof(int);
  2. printf("int类型的变量占用多少个字节:%d",len);

b.sizeof(变量名) 就会返回这个变量在内存中占据多少个字节

  1. int num = 100;
  2. int len1 = sizeof(num);
  3. printf("num变量占用多少个字节:%d\n",len1);

c.sizeof(常量) 就会返回这个常量数据在内存中占据的字节数

常量:直接写1个数据,这个数据就是常量

  1. int len2 = sizeof(12.24f);
  2. printf("常量12.24占用%d个字节\n",len2);

 注意:

a.

12.24f 这个数据的类型是float类型的,在内存中就只会占据4个字节

12.12 这个数据是double类型的,在内存中就会占据8个字节

这就是f和不f的区别

b.

char类型的变量在内存中占据1个字节,char类型的常量在内存中占据4个字节(char类型的常量是int数字,占用4个字节)

  1. char ch = 'a';
  2. int len1 = sizeof(ch);
  3. printf("ch字符占用%d个字节\n",len1);

  1. int len2 = sizeof('a');
  2. printf("字符常量占用%d个字节\n",len2);

2)使用简便方法

在使用sizeof的时候,某些情况下可以直接省略小括弧

当判断的东西不是数据的类型可以省略小括弧

sizeof    变量名

sizeof    常量

 但是这样是不行的,sizeof 数据类型,这样是不行的,这个时候必须要加小括弧

  1. int num = sizeof 10;
  2. printf("num变量占用的字节数是%d\n",num);
  3. char ch = 'a';
  4. int num2 = sizeof ch;
  5. printf("num2的值是%d\n",num2);

 

5.变量在内存中占据的字节数,会因为系统版本,编译器的不同而发生变化

18.char类型的深入

1)任何数据在内存中都是以其二进制的补码形式存储的,思考:char变量是如何存储的?

2)字符数据在内存中如何存储?

字符数据在内存中存储的就是这个字符所对应ASCII码的二进制补码

char ch = 'a';

这个时候ch变量中存储的是'a'的ASCII码97的二进制补码

3)所以,我们可以直接为char变量赋值1个整数,因为你char变量中本来存储的就是1个整数

char ch = 97;

也可以使用%d输出变量的值,因为char变量中本来存储的就是整数

4)char变量是1个有符号数,最高位表示符号位

5)%c读取的时候,先从变量中读取出存储在变量中的整数,然后再去ASCII码表中去查找这个整数对应的字符,再去显示这个字符

19.有符号和无符号

1)unsigned修饰符

我们刚刚讲的,int 、short 、long、long long 这些全是最高位表示符号位,实际上表示的数据少了一位,因为最高位用来表示符号位了

存储1个人的年龄,int 4个字节,最高位是符号位,剩下31位表示数据

我们希望最高位不要用来表示符号位,而是用来参数数据的表示,这个时候就有32位来表示数据,最大值翻了一倍,但是最小值就成了0

2)声明int变量的时候为这个int变量加1个修饰符unsigned,表示这个变量的最高位不要用来表示符号位,而是参与到数据的表示之中

unsigned int num = 100;

那么这个变量最小值就成了0,没有正负,但是最大值比原来翻了两倍

  1. unsigned int num5 = 2300000000;
  2. printf("%u",num5);

使用%u输出unsigned int变量的值

3)输出修饰符

unsigned int ----> %u

unsigned short ----> %hu

unsigned long ------> %lu

unsigned long long -----> %llu

4)signed

表示最高位用来做符号位,实际上默认就是这样的,signed写不写都一样,实际上默认int ,short ,long ,long long最高位都表示符号位

20.第八天的作业

1)公司里面假设有两个开发人员:张三、李四,李四负责编写一些整数直接的算数运算函数,包含了:加法、减法、乘法、除法

张三负责编写main函数,并且要用到李四编写的算数运算函数

根据上面的情景,合理设计1套程序

> 文件个数不限

> 文件名自拟

  1. int main(int argc, const char * argv[]) {
  2. int a = addSum(10, 20);
  3. printf("a的值是:%d\n",a);
  4. return 0;
  5. }

2)请计算97在计算机内存中是以什么二进制来存储的

97的二进制源码为:1100001,97是1个正数,所以补码也为1100001

3)请计算出下列数据的源码、反码、补码

13  19  -81  121   -87   -12  67

原码:最高位表示符号位,剩下的位数,是这个数的绝对值的二进制

正数的绝对值是自己,负数的绝对值去掉负号

反码:正数的反码就是其原码,负数的反码就是在其原码的基础之上,符号位不变,其他位取反

补码:正数的补码就是其原码,负数的补码就是在其反码的基础之上加1

a.13的源码为:00000000 00000000 00000000 00001011

   13的反码为:00000000 00000000 00000000 00001011

   13的补码为:00000000 00000000 00000000 00001011

b.19的源码为:00000000 00000000 00000000 00010011

   19的反码为:00000000 00000000 00000000 00010011

   19的补码为:00000000 00000000 00000000 00010011

c.-81的源码为:10000000 00000000 00000000 01010001

  -81的反码为:11111111 11111111 11111111 10101110

  -81的补码为:11111111 11111111 11111111 10101111

d.121的源码为:

第九天  数组​​​​​​​

1.思考这样一个问题 

有1个学习小组,输入每一个人的成绩,输入完毕之后,请将这5个人成绩打印出来,不是输入1个打一个,而是全部输入完毕,再一起打印出来

第一种看似解决的方案

  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. for (int i = 0; i<5; i++) {
  4. int score = 0;
  5. printf("请输入第%d个同学的成绩:",i+1);
  6. scanf("%d",&score);
  7. printf("score = %d\n",score);
  8. }
  9. return 0;
  10. }

打印结果为:

这样做是不行的,应该想办法将5个成绩全部存储起来

第二种解决方案:

  1. int score1 = 0,score2 = 0,score3 = 0,score4 = 0,score5 = 0;
  2. for (int i = 0; i<5; i++) {
  3. printf("请输入第%d个同学的成绩:",i+1);
  4. if(i==0) {
  5. scanf("%d",&score1);
  6. }else if (i == 1) {
  7. scanf("%d",&score2);
  8. }else if (i == 2) {
  9. scanf("%d",&score3);
  10. }else if (i == 3) {
  11. scanf("%d",&score4);
  12. }else if (i == 4) {
  13. scanf("%d",&score5);
  14. }
  15. }
  16. printf("score1 = %d\n score2 = %d\n score3 = %d\n score4 = %d\n score5 = %d\n",score1,score2,score3,score4,score5);

 这种方案可以解决,但是当数据多时,不可行

2.探讨问题的解决之道

上面的第一种方案是不行的,是因为我们的变量只有1个,而根据我们之前讲 的,一个变量只能存储1个数据,当我们为变量赋值的时候,如果变量中已经有值,新值就会将旧值覆盖掉,所以,上面的代码确实为score变量赋值了5次值,但是这个变量中只能保持最后1次赋的值

天马行空:

如果有1个变量,可以存储多个数据,并且多个数据直接和谐相处,并不将各个的数值覆盖

3.数组

1)作用:存储多个数据,并且存储的多个数据之间和谐的相处,与我们之前学习的普通变量的最大的区别:普通变量只能存储1个数据,赋值的时候,新值会覆盖掉旧值,而数组,可以存储多个数据,存1个数据进去,之前的数据还在

2)顾名思义:数组是数据的集合

3)数组的特点:

a.可以存储多个数据

b.1个数组当中只能存储类型相同的多个数据,是我们在创建数组的时候指定的

c.数组中可以存储的数据的个数是固定的,是我们在创建的时候指定的

d.存储在数组中的数据太方便管理了

4.务必要知道的

1)数组是来存储多个数据的

2)当你有多个数据需要存储的时候,就可以使用数组

5.如何声明1个数组

1)在创建数组之前,要先确定两点

a.确定存储的这多个数据的类型

b.这个数组最多可以存储多少个数据

2)声明数组的语法

存储的多个数据的类型,数组名称[这个数组最多可以存储多少个数据]

int arr[5];代表创建了1个数组,这个数组的名称叫做arr,这个数组最多存储5个数据,每一个数据的类型必须是int类型的

double arr[4];代表创建了1个数组,这个数组的名称叫做arr,这个数组最多存储4个数据,每一个数据的类型必须是double类型的

3)数组在内存当中是如何创建的------->形象理解版本

实际上可能不是这样的,但是为了方便理解,就这样讲,这样理解不会有任何问题

int arr[3];

a.这个数组的名称叫做arr,不是arr[3];

b.数组也是一个变量

c.这个数组的类型是int数组类型的,不是int

在内存中如何创建数组的呢?

a.先在内存中声明1个数组变量arr

b.然后将这个数组平均的划分为3个等分

c.每一个小空间的类型都是int类型的

d.真正存储数据的是数组里面的小空间

Q1:将数组划分为多少个小空间?

根据声明数组的时候,数组名称后中括弧的那个数来决定的

Q2:每一个小空间是什么类型的?

根据声明数组的时候,写的那个数据的类型来决定的

6.几个专业的术语

1)元素:数组中的每一个小空间,就叫做数组的元素

2)下标/索引:为了区分数组的每一个元素,C系统就为每一个元素编了一个号码,这个号码从0开始,依次递增,这个号码叫做这个元素的下标

3)长度:指的是数组当中元素的个数,也就是这个数组当中最多可以存储多少个数据

7.如何往数组当中存储数据呢?

1)数组当中存储数据的是数组的元素,而不是整个数组,数组名代表整个数组,所以不能直接为数组赋值

int arr[3];

arr = 30;//这样赋值是不对的

a.情理上:因为arr代表整个数组,你把10赋值给整个数组,就不知道给谁

b.语法上:10是Int类型的,arr是int数组类型的,类型不同,当然不能赋值了

2)数组中真正存储数据的是元素,所以我们应该将值赋值给元素

int arr[3];

而数组中有多少个元素,你必须要确定赋值给哪一个元素,通过下标来确定

语法:数组名[元素下标] = 数据

arr[1] = 100; 将100赋值给arr数组中下标为1的那个元素

注意:printf函数的参数是字符串类型的,如果类型错误,也是会报坏地址的错误类型

8.数组的元素到底是1个什么东西?

 int arr[4];

元素的本质,其实就是1个普通类型的变量,上面这个数组,有4个元素,其实这4个元素就是4个int类型的变量,这4个int类型的变量合起来就成了arr数组,我们为数组的元素赋值,其实也就是为1个普通类型的变量赋值。

数组之所以可以存储多个数据,是因为数组中有多个元素,数组是存储在元素当中的,而元素就是1个普通的变量。

所以我们元素重复赋值的时候,新值干掉旧值

9.为元素赋值的时候注意两点

1)为元素赋值的时候,赋值的数据的类型要和元素的类型一致,当赋值的数据的类型和元素的类型不一致的时候,会做自动类型的转换

当给int类型的数据赋值十六进制数据的时候,里面存放的是对应的十进制数据

2) 下标不能越界

当我们为数组的元素赋值的时候,如果下标越界,其实可以赋值,只不过就不是为数组的元素赋值

int arr[3]; arr[3]=1000;

为数组的下标为3的元素进行赋值,而arr数组没有下标为3的元素,那么这个时候,就找到数组屁股后面的那空间,而这个空间并不是数组的,有可能这个空间无人使用,也有可能被别的程序使用,也有可能被系统使用,如果这个时候为这个空间赋值,轻则别的程序崩溃,重则系统崩溃。所以我们在使用下标访问数组元素的时候,下标不要越界

数组的下标的范围是0到数组的长度-1

10.如何取出存储在数组中的数据

1)我们要取出存储在数组中的元素的数据,而不是取整个数组

2)如果要取出数组中的元素的值,那么就必须要确定到底要取哪一个,通过下标来确定

数组名[下标]

int num = arr[1];

就是取出arr数组中下标为1的元素的值,赋值给num变量

3)需要注意的是:下标不能越界,因为越界取出的值就是别人的值

11.遍历数组

将数组中的每一个元素的值打印出来。

遍历的思路:

将数组的每一个元素的下标遍历出来,因为数组的下标是有规律的, 0-----数组的长度-1

只需要将0----数组的长度-1范围内的整数遍历出来,作为下标就可以取出来了

  1. int arr[4];
  2. arr[0] = 'a';
  3. arr[1] = 0x1234;
  4. arr[2] = 10.13;
  5. arr[3] = 20;
  6. for (int i = 0; i<4; i++) {
  7. printf("arr[%d]的值是:%d\n",i,arr[i]);
  8. }

-------->关于数组的基本语法全部讲解完毕<------------

1.数组的作用

2.数组的特点

3.数组的声明

4.形象的理解数组在内存中是如何声明的

5.元素 下标 长度

6.如何往数组中存储数据

7.如何取出存储在数组中的数据

8.如何遍历数组中的元素

12.使用数组的时候要注意的问题

1)关于数组的长度

a.在声明数组的时候必须要指定数组的长度(这个是第一种赋值方式)

b.数组的长度可以是常量、变量、表达式(长度就是表达式的结果)、字符(长度就是其ASCII码)

c.数组的长度不能是1个小数

d.数组的长度可以是1,也可以是0,虽然这样是可以的,但是我们平常不会这样用

e.数组的长度也可以是宏,宏值就必须是一个整数

2)关于数组的元素的默认值的问题

当我们声明1个数组,没有为数组的元素赋值的时候,那么这个时候数组的元素是有值的,值是1个垃圾值

3)关于数组的元素的初始化

a.先声明数组,然后再使用下标为逐个赋值

int arr[3]; arr[0] = 100; arr[1] = 200; arr[2] = 10;

b.在声明数组的同时就初始化数组的元素,int arr[3] = {10,20,30};

将数组的每一个元素的值依次的写在后面的大括弧当中。

使用这种方式初始化,数组的长度就不能使用变量了

为什么?

int arr[3] = {10,20,30}; 在编译器编译的时候,是将这个代码换成了上面那种傻代码

int arr[3];

arr[0] = 10;

arr[1] = 20;

arr[2] = 30;

问题:

int len = 3; //这个代码是在运行的时候执行

int arr[len] = {10,20,30}

在编译器编译的时候,无法确定数组的长度,这个时候赋值就可能越界,所以为了保证一定不会出错,干脆这种方式初始化的时候不允许使用变量来表示长度,但是使用宏是可以的

c.在使用第二种方式初始化的时候,可以省略数组的长度

int arr[] = {10,20,30,40,50,60,70};

这个时候,数组的长度是由大括弧当中的数组的个数来决定的,大括弧当中有多少个数据,那么数组的长度就是多少

d.第四种初始化的方式,只为数组的前面的元素赋值

int arr[3] = {10};

这个时候,数组的第0个元素的值是10,其他元素的值自动的被初始化为0

所以,如果我们要将1个数组当中的所有的元素初始化为0,就可以int arr[3] = {0};

e.第五种初始化方式,指定下标的初始化

int arr[3] = { [1] = 10, [2] = 20},其他的元素的值就自动的初始化为0

13.再次处理题目

有1个学习小组,输入每一个人的成绩,输入完毕之后,请将这5个人成绩打印出来,不是输入1个打一个,而是全部输入完毕,再一起打印出来

自己写的​​​​​​​:

  1. int a,b,c,d,e;
  2. printf("请输入5个学生的成绩:");
  3. scanf("%d",&a);
  4. scanf("%d",&b);
  5. scanf("%d",&c);
  6. scanf("%d",&d);
  7. scanf("%d",&e);
  8. int arr[5] = {a,b,c,d,e};
  9. printf("5个学生的成绩分别是:%d,%d,%d,%d,%d\n",arr[0],arr[1],arr[2],arr[3],arr[4]);

标准答案:

  1. double scores[5] = {0};
  2. //2.循环接收输入5个成绩,将每一个成绩存储到数组的元素当中
  3. for (int i = 0; i<5; i++) {
  4. printf("请输入第%d个同学的成绩:",i+1);
  5. scanf("%lf",&scores[i]);
  6. }
  7. //循环完毕之后,数组中就存储了5个成绩了
  8. for (int i = 0; i<5; i++) {
  9. printf("第%d名同学的成绩是:%lf\n",i+1,scores[i]);
  10. }


​​​​​​​

成绩输入完毕之后,由于我们这次考试的难度较大,所以为每一位不及格的成绩+5分,然后再将所有的成绩输出

  1. //1.先声明1个长度为5的double的数组,用来存储数据
  2. double scores[5] = {0};
  3. //2.循环接收输入5个成绩,将每一个成绩存储到数组的元素当中
  4. for (int i = 0; i<5; i++) {
  5. printf("请输入第%d个同学的成绩:",i+1);
  6. scanf("%lf",&scores[i]);
  7. if(scores[i]<60) {
  8. scores[i] += 5;
  9. }
  10. }
  11. //循环完毕之后,数组中就存储了5个成绩了
  12. for (int i = 0; i<5; i++) {
  13. printf("第%d名同学的成绩是:%lf\n",i+1,scores[i]);
  14. }

14.数组在内存当中的存储方式 

1)变量在内存当中的存储形式

不同类型的变量在内存当中占据不同的字节数,1个变量占用的字节一定是连续的

int 4

double 8

float 4

char 1

2)在为变量分配字节空间的时候,是从高地址

​​​​​​​2.垃圾值的由来

1)我们声明1个局部变量,如果没有为这个局部变量初始化,那么这个局部变量中是有值的,值是1个垃圾值,随机数

2)变量的回收

在大括弧执行完毕之后,定义在这个大括弧中的变量就会被系统立即回收,声明变量的时候,其实是这样的,系统会为你从高地址向低地址分配指定字节数的连续空间

如何回收?

当变量回收的时候,其实就是告诉系统变量占用的字节不在使用了,可以分配给别的变量的,变量所占用的字节的数据是不会被清空的,当在声明1个变量的时候,这个新变量有可能就是刚刚被回收的那个变量所占用的空间,那么这个时候,这个新变量中是有值的,值就是上次那个变量遗留下来的值,这就叫做垃圾值

所以,我们声明1个局部变量,最好是先为其初始化为0

3)全局变量

​​​​​​​当将全局变量声明出来以后,系统会自动的将全局变量中的数据清零。

第十一天 指针

一、指针

1.指针是C语言的灵魂

2.变量在内存当中的存储

1)不同类型的变量在内存当中占据着不同的字节空间

int 类型占据着连续的4个字节的内存空间

double 类型占据着连续的8个字节的内存空间

float 类型占据着连续的4个字节的内存空间

char 类型占据着1个字节的内存空间

2)内存中存储数据的最小基本单位是字节,每1个字节都有1个内存地址,这个地址是一个十六进制的数

3)声明1个变量,在内存当中是从高字节向低字节分配连续的指定字节数的空间

int num = 10;

4)任何数据在内存当中都是以其二进制的补码形式存在的,低位存储在低字节,高位存储在高字节

5)变量的值:存储在变量中的数据,叫做变量的值

      变量的地址:1个变量是由1个或者多个字节组成的,组成这个变量的低字节的地址,就是这个变量的地址

6)如何取出变量的值

int num = 10;

直接写上变量的名字,就可以取到变量中的值

如何取出变量中的地址

使用&运算符,&变量名,这个表达式的结果就是这个变量的地址

%p 格式控制符输出变量的地址

7)什么是指针?

变量的地址就叫做指针,指针就是地址

二、指针变量

1)不管是个什么东西,首先它是1个变量。指针变量就是专门用来存储地址的变量,专门用来存储另外1个变量的地址的。

2)这么做的好处

访问1个变量的方式主要分为两种

a.直接访问

int num = 10;

num = 20; //直接访问这个num

b.间接访问

可以通过指针变量找到这个指针变量指向的变量,通过指针变量就可以间接的访问这个指向的变量

通过指针变量可以间接的访问指针变量指向的另外1个变量

3)如何申明1个专门用来存储地址的指针变量呢?

a.我们之前学习的普通变量是如何申明的?

数据类型 变量名; int num;

b.声明指针变量的语法

数据类型* 指针变量的名称;

int* p1;

代表声明了1个指针变量,这个指针变量的名字叫做p1,这个指针变量的类型是int*,读作int指针

这个*代表 这个变量不是1个普通的变量,而是1个专门用来存储地址的指针变量,所以这个p1指针变量只能存储地址

int * p1;

double* p2;

float* p3;

char* p4;

指针的类型:有哪些普通的类型就可以有哪些指针的类型

c.声明的时候注意

*的位置 可以与数据类型挨在一起,也可以和指针变量名挨在一起,也可以单独写在中间

int* p1;

int *p1;

int * p1;

d.指针变量是用来存储另外1个变量的地址,但是1个指针变量并不是可以存储任意类型的变量的地址,而是有限定的,只能存储和这个指针类型相同的普通变量的地址

int* p1 ;p1变量中只能存储int类型的变量的地址;

double* p2;p2变量中只能存储double类型的变量的地址

float* p3;p3变量中只能存储float类型的变量的地址

char* p4;p4变量中只能存储char变量的地址

三、指针的使用

1)指针变量是用来存储地址的。

2)指针变量是用来存储另外1个变量的地址,并且指针可以存储的另外1个变量的地址,这个变量的类型是限定的

3)正确的初始化的步骤

a.先取出变量的地址

使用&取地址符号就可以取出变量的地址。要打印地址,使用格式控制符%p

b.将取出来的变量的地址赋值给指针变量

int num = 10;

int *p1 = #

这个时候,p1指针变量的值就是num变量的地址,那么我们就说,p1指针指向了num变量

  1. int age = 10;
  2. int *p1 = &age;
  3. printf("p1 = %p\n",p1);
  4. printf("age的地址是:%p\n",&age);
  5. //取出p1指针变量的地址
  6. printf("p1的地址是:%p\n",&p1);

4)指针变量只能存储和指针变量类型相同的普通变量的地址,否则就会出问题,起码我们现在看到的是在编译的时候会报一个大警告。

5)如果直接写变量名,操作的就是这个变量,你可以为这个变量赋值或者取值

&变量名;这是一个表达式,&是1个运算符,叫做取地址运算符,这个表达式的结果,是这个变量的地址

6)指针变量在内存当中也有1个地址,因为指针变量也是1个变量,所以,我们也可以使用&符号取出指针变量的地址

 int *p1;

p1 操作的是p1这个指针变量,可以取p1的值,也可以为p1赋值,

&p1,拿到的是p1的地址

四、使用

1.指针变量的使用

1)指针变量中存储的是另外1个普通变量的地址

这么做的好处:在于可以使用指针间接的操作指针指向的变量

2)Pointer 建议大家在声明指针变量的时候以p开头

3)操作变量的方式

a.直接访问

int num = 10;

num = 20;

直接操作这个变量

b.间接访问

当1个指针指向了另外1个变量的时候,那么就可以通过指针来间接的操作这个变量

操作1个变量有两种形式:赋值、取值

4)使用指针间接的操作指针指向的变量

格式:*指针变量名;代表这个指针指向的变量

int num = 10;

int *p1 = #

*p1 代表p1指针指向的变量,也就是num

*p1完全等价于num

给num赋值:

  1. int age = 10;
  2. int *p1 = &age;
  3. *p1 = 100;
  4. printf("age = %d\n",age);
  5. printf("age的值是:%d\n",*p1);

将100赋值给p1指针指向的变量,也就是num

这个时候可以通过这种方式间接的去为指针指向的变量赋值或者取值

*变量名;这个表达式的结果是指针指向的变量,拿到指针指向的变量就可以为指针指向的变量赋值或者取值

5)注意

a.*指针变量 就完全代表指针指向的变量,所以通过这种方式为指针指向的变量赋值的时候,数据类型不同,会做自动转换

---------------------如何使用指针间接的操作指针指向的变量----------

2.使用指针变量的时候注意的问题

1)指针变量也是1个变量,所以可以批量声明指针变量

int *p1,p2,p3;

这样声明的话,只有p1是int指针,p2和p3是int类型的

如果希望全是指针int * p1,*p2,*p3

五、野指针

我们声明1个指针变量,如果没有为其初始化,那么这个时候这个指针变量中是有值的,垃圾值,随机数。这个时候,这个指针变量就有可能指向了1块随机的空间

这块空间:有可以无人使用

                   有可能别的程序在用

                   有可能系统再用

这个时候去访问指针指向的变量的时候,就会报错,BAD_ACCESS错误

像这样的指针我们叫做野指针

六、NULL值

我们声明1个指针,如果不初始化这个指针,这个指针就是1个野指针,就指向了1块随机的空间,那么这个时候,如果你通过这个指针访问指向的随机的空间的时候,是相当危险的,如果是取值还罢了,如果赋值就相当的危险,就可能会造成别的程序崩溃。

所以我们建议大家,声明1个指针变量以后,最好为其初始化,如果你没有变量的地址初始化这个指针变量,那你就初始化1个NULL值,NULL值代表指针变量不指向内存的任何地址,谁都不指,这个NULL完全等价于0,所以,你也可以直接赋值给1个指针变量0

如果1个指针变量的值是NULL值,这个时候通过指针变量去访问指向的变量的时候,100%报错

七、多个指针指向同1个变量

int num = 10;

int *p1 = #

int *p2 = p1;

*p1 = 100;

p1和p2指针都指向了num变量

这个时候无论是通过*p1还是*p2,都是访问num变量

八、malloc函数

1.内存中的五大区域

栈:局部变量

堆:堆区的字节空间允许程序员手动去申请

BSS段:未初始化的全局变量、静态变量

数据段:已经初始化的全局变量、静态变量和常量数据

代码段:存储代码的

2.如何向堆区申请字节空间来使用:

1)我们在堆区申请的字节空间,如果我们不主动释放,那么系统是不会释放掉的,除非程序结束了

2)在堆中申请字节空间的步骤:

a.申请

b.使用

c.释放

3)如何在堆区申请指定字节数的字节空间呢?

malloc()

calloc()

realloc()

这3个函数都是和申请字节空间有关的,这几个函数的声明,是放在1个叫做stdlib.h的系统头文件里的

4)malloc函数

malloc(size_t __size)

a.malloc函数参数只有1个,size_t类型的,也就是unsigned long ,最小值为0,没有正负

b.作用:向堆空间申请指定字节的空间来使用

c.参数代表的意义,向堆内存申请多少个连续的字节空间

d.做的事情:在堆内存中申请连续的参数个字节空间

e.返回值是:void *

 void *:代表没有类型的指针(地址),可以是float,可以是int

返回的是创建的空间的第一个字节的地址,地址是没有类型的,只是返回了第一个字节的地址,没有说这个指针是什么类型的

f.我们应该使用什么类型的指针变量来保存malloc函数返回的地址呢?

那就要看你想要如何去操作申请的这些字节空间。如果你想要1个字节1个字节的操作,那么就使用char指针,如果你想要4个字节4个字节的操作,并且当作整型来操作,那么就要使用int指针,如果你想要8个字节8个字节的操作,那么就要double类型的指针。如果你想要4个字节4个字节的操作,并且当作单精度浮点型来操作,那么就要使用float类型指针

就要看你想要如何操作申请的这些字节空间

int *p1 = malloc(8)

*p1 = 100; 操作的时候是以4个字节为基本单位

char *p1 = malloc(8)

*p1 = 100; 操作的时候是以1个字节为基本单位

  1. int *p2 = malloc(24); //相当于在内存中创建了1个长度为6的整型数组
  2. *p2 = 10;
  3. *(p2+1) = 20;
  4. * (p2+2) = 30;
  5. *(p2+3) = 40;
  6. *(p2 +4) = 50;
  7. *(p2+5) = 60;
  8. for (int i = 0; i<6; i++) {
  9. printf("%d\n",p2[i]);
  10. }

这样的话,我们就可以在堆内存中申请任意字节数的空间来使用,通过指针来使用申请的空间

g.在堆区申请的字节空间是从低地址向高地址分配的,每次申请的字节地址都是从0开始,每一次申请的字节空间不一定挨在一起,但是,每一次申请的指定个字节,这些字节一定肯定是连续的

  1. int *p1 = malloc(4);
  2. int *p2 = malloc(4);
  3. printf("p1 = %p\n",p1);
  4. printf("p2 = %p\n",p2);

0x600000008010 和0x600000008020是差16个字节

h.在堆区申请的字节,里面是有值的,值是垃圾值,不会自动清零

  1. int *p1 = malloc(12);
  2. for (int i = 0; i<3; i++) {
  3. printf("p1[%d] = %d\n",i,p1[i]);
  4. }

i.在向堆区申请字节空间的时候,有可能会申请失败,如果申请失败,返回值是NULL 

  1. int *p1 = malloc(120);
  2. if(p1) {
  3. //说明申请字节空间成功
  4. for (int i = 0; i<30; i++) {
  5. p1[i] = i*10;
  6. }
  7. for (int i = 0; i<30; i++) {
  8. printf("p1[%d] = %d\n",i,p1[i]);
  9. }
  10. }

j.申请的空间使用完毕之后,一定要记得释放,释放申请的堆空间;   

free(指针);

如果没有free,程序结束才会释放,记住,一定要释放,因为我们的程序有可能会运行很久

第十二天  指针的深层理解

1.const修饰基本数据类型

1)const是1个关键字,是来修饰我们的变量的,也就是说在声明变量的同时,可以使用const关键字来修饰

const int num = 10;

一般情况下来说,被const修饰的变量具备一定程度上的不可变性

2)const修饰基本数据类型的变量

基本数据类型:int double float char

a.const int num = 10;这个时候,num变量的值只能去取值,而不能去修改

b.int const num = 10 效果同上

3)const修饰数组

a. const int arr[4] = {10,20,30,40}; 数组的元素的值是不能修改的

b.int const arr[4] = {10,20,30,40}

4)const修饰指针

a.const int *p = &num 无法通过指针去修改指针指向的变量的值,但是如果直接操作变量是可以的,但是指针变量的值可以改,可以把另外1个变量的地址赋值给这个指针

  1. int num1 = 10;
  2. const int *p1 = &num1;
  3. // *p1 = 100;
  4. num1 = 100;
  5. int age = 20;
  6. p1 = &age;

例如上面,p1的值可以修改,但是*p1的值不可以修改,不可以去修改指针指向的变量的值,但是p1可以修改修改,可以重新赋值其他变量的新的地址

b.int const *p2 = # 把const放到int和*中间

效果同上

c.int * const p3 = &num ;把const放到int *的后面

p3的值不能修改,但是可以通过p3去修改p3指向的变量的值


​​​​​​​

d.  int const * const p4 = #

既不能修改p1的值也不能通过p1去修改p1指向的变量的值

2.const的使用场景

1)const的特点:被const修饰的变量,是只读变量,只能取值,而不能改值,所以const变量的值,至始至终都不会发生变化

2)当某些数据是固定的,在整个程序运行期间都不会发生变化的,并且你不允许别人去修改的,那么这个时候,我们就可以使用const

案例一:当我们设置某些数据不想被修改的时候,比如设置的固定的宽和高

const wight : 200

const height : 400

案例二:

sizeof可以确定数组的大小(元素的个数),例如int类型a数组中有10个整数,那么sizeof(a)的值就是40,因为int类型通常占用4个字节,所以a数组的元素个数为40/4=10个

  1. void test(int arr[],int len) {
  2. for (int i = 0; i<len; i++) {
  3. printf("%d",arr[i]);
  4. }
  5. }
  6. int arr1[] = {102,137,135,167,352};
  7. test(arr1, sizeof(arr1)/sizeof(int));

我们调用一个函数,如果进行传参的时候不想让函数修改我们的值,就需要使用const来修饰,比如上面的函数中我们就可以使用const来修饰,这样函数中就不能对我传过去的数据进行修改

  1. void test(const int arr[],int len) {
  2. for (int i = 0; i<len; i++) {
  3. printf("%d",arr[i]);
  4. }
  5. }

3)当1个函数的参数是指针的时候,这个时候,函数的内部是有可能会修改实参变量的值,这个时候,函数想传递给调用者1个信息,你放心大胆的传给我吧,我肯定不会修改,那么这个时候,就可以给参数加1个const

  1. void test1(int *p1) {
  2. }
  3. test1(&num1);

六、结构体

1.使用结构体来创建新的数据类型

1)语法格式:

struct 新类型名称

{

      //在这个里面写上,你创建的新类型是由哪些变量联合而成的

    数据类型1 小变量名称1;

    数据类型2 小变量名称2;

    数据类型3 小变量名称3;   

};

例如创建一个Student类型的变量

  1. struct Student{
  2.   char *name;
  3.   int age;
  4.   int score;
  5. float height;
  6. };

代表我们新创建了1个数据类型,这个数据类型的名称叫做struct Student,这个新的类型是由1个char *、int、int、float的小变量联合而成的

2)声明结构体类型的变量

语法格式:

struct 新类型名称 变量名;

 struct Student stu;

代表声明了1个struct Student类型的变量,变量名称叫做stu,这个时候,stu才是1个变量,才会在内存中申请空间

这个变量中,是由这个新的结构体类型规定的小变量组合而成的

3)为结构体类型的变量赋值

语法格式:

变量.小变量 = 数据;

专业术语:变量.成员 = 数据

  1. struct Student{
  2.   char *name;
  3.   int age;
  4.   int score;
  5. float height;
  6. };
  7. struct Student stu;
  8. stu.name = "jack";
  9. stu.age = 17;
  10. stu.score = 100;
  11. stu.height = 189.8;
  12. printf("姓名:%s,年龄:%d,成绩:%d,身高:%.2f\n",stu.name,stu.age,stu.score,stu.height);

4)什么时候我们要定义自己的结构体

当我们要保存1个数据,但是发现这个数据是1个大数据,因为这个数据是由其他的小数据联合起来组成的,那么这个时候,先使用结构体类自定义这个数据类型是由哪些小变量合成的,再根据这个结构体类型声明的变量,来保存数据。

六、枚举

1.枚举的使用

1)某些变量的值,是限定的,变量的值只能是指定的这几个值中的任意1个,除此之外其他不行

新的需求:变量的取值只能是指定的几个值中的任意1个,除此之外其他的不行

2)C语言默认没有提供可以限定取值类型的变量,那么我们就定义具备限定取值的类型,枚举就能做这个事情。

作用:支持程序员新创建1种数据类型,这个数据类型的变量的取值被限定

2.语法格式

enum 新类型名称

{

 限定值1,限定值2,限定值3,....

}

例如:

  1. enum Direction {
  2. East, //0
  3. North,//1
  4. South,//2
  5. West,//3
  6. };

表示新创建了1个数据类型,这个数据类型的名称叫做,enum Direction,可以声明这个类型的变量,这个变量就只能存储这其中指定的任意1个

3.声明枚举类型的变量

1)语法格式:

enum 枚举类型名称 变量名;

例如:

  1. enum Direction dir = North;
  2. int len = sizeof(dir);
  3. printf("len的值是:%d\n",len);
  4. printf("%d\n",dir);

表示声明了1个变量,变量的名称叫做dir,变量的类型叫做enum Direction

这个变量的特点:只能存储这个枚举类型限定的取值之一

2)枚举变量的初始化

只能为这个枚举变量赋枚举类型限定的取值之一。

4.使用场景:

只能取限定值,比如QQ状态:在线、离线、隐身、忙碌

5.注意

1)枚举的作用域

如果将枚举类型定义在函数的内部,那么这个类型就只能在这个函数的内部使用

如果你希望将这个枚举的类型给所有的函数使用,那么就将这个枚举类型定义在函数的外面,最顶部。

2)每1个枚举值/枚举项,都有1个对应的整形的数,默认从0开始,依次的递增

3)无论是什么类型的枚举变量,都是占用4个字节,枚举变量中真正存储的是枚举值所对应的整形的数,所以我们才可以使用%d,如果打印枚举值,是没有办法的,只能存储整形的数,如果就要打印,只能进行判断

4)虽然我们赋值的时候可以给一个整数,但是我们并不会这么做,这样做的话代码的可读性会很差,给枚举值,可读性就会变得很高

5)默认情况下,每1个枚举值对应的整型的数是从0开始的,一次递增,但是我们还可以手动的指定每1个枚举值所代表的整数

  1. enum Direction {
  2. East = 10, //0
  3. North = 20,//1
  4. South = 30,//2
  5. West = 40,//3
  6. };

6.使用枚举的规范

枚举类型的名称命名规范:首字母大写,每1个单词的单词的首字母大写

枚举值的命名规范:枚举值的名称都以枚举类型的名称开头

  1. enum Direction {
  2. DirectionEast = 10, //0
  3. DirectionNorth = 20,//1
  4. DirectionSouth = 30,//2
  5. DirectionWest = 40,//3
  6. };

七、typedef

1.定义

typedef--->type define  类型定义

作用:为1个已经存在的数据类型取1个别名,如果我们想要使用这个数据类型,直接使用这个别名就可以了

2.语法格式


​​​​​​​typedef 已经存在的数据类型 别名

  1. typedef int lj;
  2. lj num = 10;

typedef int lj; 为int类型取了一个别名,叫做lj

如果我们要使用int类型,就可以直接使用lj

这个时候lj完全等价于int

因为lj是int的1个别名

typedef char* string;

将char *类型取1个别名,叫做string

所以我们就可以使用string代替char *

  1. typedef char * string;
  2. string h = "jack";

3.什么时候使用typedef

当数据类型很长的时候,就可以为这个数据类型取1个短一点的别名,这样用起来就很方便

4.使用typedef来为结构体类型取1个短别名

1)方式一:

先声明结构体类型,然后使用typedef为这个结构体类型取1个短别名

  1. struct Student {
  2. char *name;
  3. int age;
  4. int score;
  5. };
  6. typedef struct Student Student;

使用:

Student st1;

2)方式二:

声明结构体类型的同时,就使用typedef来为结构体类型取1个短别名

  1. typedef struct Student {
  2. char *name;
  3. int age;
  4. int score;
  5. }Student;

3)方式三:

声明匿名结构体的同时,就使用typedef来为结构体类型取1个短别名,这个是最常用的方式

  1. typedef struct {
  2. char *name;
  3. int age;
  4. int score;
  5. }Student;

5.使用typedef为枚举类型取1个短别名

1)方式一:

先声明1个枚举类型,然后使用typedef来为枚举类型取1个短别名

  1. enum Direction {
  2. DirectionSouth,
  3. DirectionEast,
  4. DirectionWest,
  5. DirectionNorth
  6. };
  7. typedef enum Direction Direction;

使用:

 Direction dir = DirectionEast;

2)方式二:

声明枚举类型的同时,就使用typedef来为枚举类型取1个别名

  1. typedef enum Direction {
  2. DirectionSouth,
  3. DirectionEast,
  4. DirectionWest,
  5. DirectionNorth
  6. }Direction;

3)方式三:

使用上面这种方式的时候,枚举的名称就没有必要写了,这是最常用的方式

  1. typedef enum {
  2. DirectionSouth,
  3. DirectionEast,
  4. DirectionWest,
  5. DirectionNorth
  6. }Direction;

第十四天 预处理指令

一、static和extern

1.这是C语言中的两个关键字,是用来修饰变量和函数

static和extern修饰局部变量的效果

static和extern修饰全局变量的效果

static和extern修饰函数的效果

2.static修饰局部变量

1)如果局部变量被static修饰,这个变量就叫做静态变量

2)静态变量不存储在栈区域,而是存储在常量区

3)当函数执行完毕之后,这个静态变量不会被回收,下次在执行这个函数的时候,当第一次执行这个函数的时候,就会将这个静态变量声明在常量区,函数执行完毕之后,这个静态变量不会被回收,后面再去执行这个函数的时候,声明静态变量的这句话,就不会在执行了,而是直接略过,直接使用这个静态变量的值。

4)static修饰静态变量,函数结束不会被回收,仍然存在,函数无论执行多少次,这个静态变量只有1份

  1. #include <stdio.h>
  2. void test(void) {
  3. int num = 0;
  4. num++;
  5. printf("num = %d\n",num);
  6. }
  7. int main(int argc, const char * argv[]) {
  8. // insert code here...
  9. test();
  10. test();
  11. test();
  12. return 0;
  13. }


​​​​​​​

被static修饰之后

  1. #include <stdio.h>
  2. void test(void) {
  3. static int num = 0;
  4. num++;
  5. printf("num = %d\n",num);
  6. }
  7. int main(int argc, const char * argv[]) {
  8. // insert code here...
  9. test();
  10. test();
  11. test();
  12. return 0;
  13. }

3.extern不能修饰局部变量

4.全局变量

1)写1个函数,最完整的步骤应该分为两步,先写函数的声明,再写函数的实现

2)我们写1个全局变量,最完整的步骤也应该分为两步,

先写全局变量的声明(只定义全局变量,而不赋值,这个就叫做全局变量的声明)int num ;

再写全局变量的定义(定义全局变量并初始化,也叫全局变量的实现)int num = 10;

  1. #include <stdio.h>
  2. int num; //全局变量的声明
  3. void test(); //函数的声明

需要注意的是,如果声明一个全局变量,声明的时候需要写上数据类型,实现的时候也需要写上数据类型,代表的还是一个定义,一个数据,但是如果定义的是局部变量,定义完之后不能写上它的数据类型,否则会重复定义,不写数据类型,直接赋值就可以

  1. int num; //全局变量的声明
  2. int num = 10; //全局变量的实现

  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. printf("num的值是%d\n",num);
  4. int sum;
  5. sum = 10;
  6. return 0;
  7. }

 这个时候,全局变量的声明可以放在使用这个全局变量的前面,全局变量的定义可以放在使用全局变量的后面,这个时候,仍然可以正常取值。

3)全局变量如果只有声明,没有定义,那么这个时候编译器会自动的帮助我们去实现这个全局变量,自动实现的时候,会将这个全局变量自动初始化为0 

4)全局变量也可以只有定义,而没有声明,但是这个时候,这个全局变量的定义必须要在使用全局变量的函数的前面

5.我们分模块开发的时候,如果要在模块中声明全局变量

1)全局变量的声明要写在.h文件中

2)全局变量的实现要写在.c文件中

3)如果将全局变量定义在模块中,这个全局变量必须要使用static或者extern来修饰

如果在当前类中调用模块中的全局变量,不使用static或者extern来修饰,会报如下的错误

如果定义在模块中的全局变量,使用extern修饰,这个模块中的全局变量就可以跨模块访问了,能正确取到值

模块中的.h文件

  1. #ifndef itcast_h
  2. #define itcast_h
  3. #include <stdio.h>
  4. extern int num;
  5. #endif /* itcast_h */

模块中的.m文件

  1. #include "itcast.h"
  2. extern int num = 10;

 main函数中对其进行调用

  1. #include <stdio.h>
  2. #include "itcast.h"
  3. int main(int argc, const char * argv[]) {
  4. printf("num的值是%d\n",num);
  5. return 0;
  6. }

此时输出的num的值是10

如果定义在模块中的全局变量,使用extern修饰,这个模块中的全局变量就可以跨模块访问

如果定义在模块中的全局变量,使用static修饰,这个模块中的全局变量就只能在当前模块中访问

使用stactic修饰

模块中的.h文件

  1. #ifndef itcast_h
  2. #define itcast_h
  3. #include <stdio.h>
  4. static int num;
  5. #endif /* itcast_h */

模块中的.m文件

  1. #include "itcast.h"
  2. static int num = 10;

在main函数中使用

  1. int main(int argc, const char * argv[]) {
  2. printf("num的值是%d\n",num);
  3. return 0;
  4. }

结果是默认值0

 如果不垮模块开发,在当前类中用static和extern用来修饰全局变量结果一样

  1. #include <stdio.h>
  2. #include "itcast.h"
  3. extern int sum = 10;
  4. void test(void) {
  5. sum++;
  6. printf("sum的值是%d\n",sum);
  7. }
  8. int main(int argc, const char * argv[]) {
  9. test();
  10. test();
  11. return 0;
  12. }

结果是

使用static来修饰

  1. #include <stdio.h>
  2. static int sum = 10;
  3. void test(void) {
  4. sum++;
  5. printf("static修饰的sum的值是%d\n",sum);
  6. }
  7. int main(int argc, const char * argv[]) {
  8. test();
  9. test();
  10. return 0;
  11. }

4)如果定义在模块中的全局变量,使用extern修饰,这个模块中的全局变量就可以跨模块访问,如果定义在模块中的全局变量,使用static修饰,这个模块中的全局变量就只能在当前模块中访问,虽然不报错,但是并没有取到真正的值

static和extern也可以在当前函数中修饰全局变量,修饰的效果一样,都可以正常取值

5.static和extern修饰函数

函数必须有声明和实现,声明之后必须实现,否则链接的时候会报找不到的错误

1)如果函数被extern修饰,这个函数可以跨模块访问

2)如果函数被static修饰,那么这个函数只能在当前模块中调用,否则无法跨模块访问

如果这个函数没有写static或者extern,那么这个函数默认就是extern

static void testHanshu(void);

  1. extern void testHanshu(void);
  2. extern void testHanshu(void) {
  3. printf("我在努力的学习\n");
  4. };
  5. int main(int argc, const char * argv[]) {
  6. testHanshu();
  7. return 0;
  8. }

 二、预处理指令

1.预处理指令的特点

1)都是以#开头

2)预处理指令都是在编译之前执行

3)预处理指令后面没有分号

4)编译链接的流程

编写代码,cc -c main.c编译成目标文件,cc 链接成可执行文件,然后执行

2.C程序从编写到编译、链接、执行的过程

1)创建1个.c的源文件,.c的源文件是c程序的源文件

2)在.c文件中写上符合c语法规范的源代码

     a.c语言严格区分大小写

     b.除了字符串常量,其他地方必须使用英文输入法

3)使用cc -c 指令去编译源文件

 格式:cc -c 源文件名称

 编译要做的事情

a.先执行源文件中的预处理指令,如果有文件包含指令,就将文件的内容拷贝到写指令的地方

...

b.检测.c文件中的语法是否符合规范

b.1 如果符合,生成.o的目标文件,就是.c文件对应的二进制指令

b.2如果不符合语法规范,就报错,不会生成.o的目标文件

4)链接

cc xx.o

c.1 为.o的目标文件添加启动代码

c.2链接函数,告诉编译器,要调用的函数在什么地方,调用的时候,去正确的地方去找

c.3链接成功之后,就会生成1个可执行文件,这个文件就是我们的程序了

磁盘上至少是4KB存储

执行程序

3.预处理指令的分类

1)文件包含指令 #include

2)宏定义:可以将1段C代码定义为1个标识,使用这个标识就可以使用这段代码

3)条件编译指令:只编译指定的C代码为二进制指令

4.宏定义

1)它是1个预处理指令,所以在编译之前执行

2)作用:可以为1段C代码定义1个标识,如果你要使用这段c代码,那么你就 使用这个标识就可以了

3)语法:

 #define 宏名 宏值

#define N 10

4)如何使用宏

在C代码中,直接使用宏的名称就可以了

int a = N+1;

5) 宏的原理

在预编译的时候,就会执行源文件中的预处理指令,会将C代码中使用宏名的地方替换为宏值

将C代码中的宏名替换为宏值的过程叫做 宏替换/宏代换

5.宏定义需要注意的点

1)宏值可以是任意的东东,宏值可以是任意的东西,在定义宏的时候,不会去检查语法

2)无论宏值是什么东西,在定义宏的时候,不会去检查语法,只有当完成了宏替换的时候,才会去检查语法,是否符合语法规范

3)如果宏值是一个表达式,那么宏值并不是1个表达式的结果,而是这个表达式本身

  1. #include <stdio.h>
  2. #define N 10
  3. #define M 10 +10 + 20
  4. int main(int argc, const char * argv[]) {
  5. // insert code here...
  6. int c = 3 * M;
  7. printf("c = %d\n",c);
  8. return 0;
  9. }

3*M算的是3*10+10+20,而不是3*(10+10+20)

4)如果宏值中包含1个变量名,那么在使用这个宏之前必须要保证这个变量已经存在

5)无法通过赋值符号为宏改值,因为宏根本就不是变量

6)宏的作用域:

a.宏定义可以在函数的内部,也可以在函数的外部

b.从宏定义的地方,后面所有的地方都可以直接使用这个宏,就算这个宏定义在大括弧里面,在这个后面,哪怕是大括弧外面都可以访问

c.默认情况下,宏从定义的地方一直到文件结束都可以使用

#undef 宏名

可以让指定的宏提前失效

7)字符串中如果出现了宏名。系统不会认为这是1个宏,而认为是字符串的一部分

字符串并不会出现宏替换

8)宏的层层替换

宏值当中我们用了另外1个宏名,那么就会先将这个宏值当中的宏名替换为对应的宏值

9)如果后面写了分号,那么就会把分号作为宏值的一部分,替换的时候会连分号一起,虽然可以,但是不建议加分号

  1. #include <stdio.h>
  2. #define N 10
  3. #define M 10 +10 + 20
  4. #define P printf
  5. #define D "%d\n"
  6. #define FOR for(int i = 0;i<10;i++)
  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. int c = 3 * M;
  4. printf("c = %d\n",c);
  5. P(D,c);
  6. FOR {
  7. P(D,i);
  8. }
  9. return 0;
  10. }

 可以宏定义无参数的函数,直接写函数名,而且如果宏定义循环的话,for里面的i值外层是可以拿到的

6.#define和typedef的区别

1)#define是1个预处理指令,在预编译的时候执行,在预编译的时候会把宏名替换为宏值

typedef是1个C代码,在运行的时候才会执行

2)#define可以将任意的C代码取1个标识符,typedef只能为数据类型取名字

7.含参数的宏

1)我们在定义宏的时候,全名是可以带参数的,在这个宏值当中,可以直接使用这个参数

2)使用含有参数的宏,那么就必须要在使用它的时候为这个宏的参数传值

3)宏代换的原理是什么?

a.先将传入的值传递给宏的参数,那么宏的参数的值就是我们传递的值

b.再把宏值当中使用参数的地方换成参数的值

c.最后,再将使用宏名的地方,替换为最后的宏值

  1. #define N(a) a+a+a
  2. #define SUM(a,b) a + b
  3. #define MAX(a,b) a > b ? a : b
  1. //带参数的宏
  2. int d = N(10);
  3. printf("d的值是%d\n",d);
  4. int f = SUM(10, 10);
  5. printf("f的值是%d\n",f);
  6. int e = MAX(30, 40);
  7. printf("e的值是%d\n",e);

 8.含参数的宏需要注意的地方

a.宏不是函数,所以宏的参数不需要加类型说明符,直接写参数名就可以了

b.我们在定义宏的时候,编译器是如何区分宏名和宏值的

#define空格宏名空格

第一个空格和第二个空格之间的内容会作为宏名,第二个空格之后内容会作为宏值

所以在写宏的时候,空格要小心

c.为带参数的宏传值的时候,是本色传递

如果传递一个变量,并不是传递这个变量的值,而是直接传递的就是这个变量字符

  1. int c = 10;
  2. int g = 20;
  3. SUM(c, g);

也只是把c,g传递给宏,而不是把10,20传递给宏

d.宏一定程度上可以实现和函数一样的效果,宏值一旦换行,就认为宏值定义结束了,所以你打算使用宏去替换函数的时候,就很恶心,只有当这个函数的代码很少的时候,只有几句的时候,就可以使用宏,代码多的情况下还是建议使用函数

三、条件编译指令

1.他是一个预处理指令,所以在预编译阶段执行

2.作用:默认情况下,我们所有的C代码都会编译成二进制代码

               条件编译指令的作用:可以让编译器只能编译指定部分的代码

3.条件编译指令的第一种用法

#if 条件

 C代码

#endif

  1. #include <stdio.h>
  2. #define N 10
  3. int main(int argc, const char * argv[]) {
  4. // insert code here...
  5. #if N == 10
  6. printf("AAAAAA\n");
  7. printf("AAAAAA\n");
  8. #endif
  9. printf("BBBBBB\n");
  10. printf("BBBBBB\n");
  11. return 0;
  12. }

执行的结果为:

当条件满足时执行条件里面的代码,当不满足时,不执行

在预编译的时候,如果条件成立,就会将其中的C代码编译成二进制指令,如果条件不成立,就不会将其中的C代码编译成二进制指令

注意:条件只能是宏,不能是变量

4.条件编译指令的第二种用法

  1. #if N >0
  2. printf("呵呵\n");
  3. #elif N > 10
  4. printf("哈哈\n");
  5. #elif N > 20
  6. printf("嘿嘿\n");
  7. #endif

#if 条件

   C代码

#elif 条件

   C代码

#elif 条件

   C代码

#else

   C代码

#endif

5.条件编译指令和if语句的一个对比

1)条件编译指令是1个预处理指令,在预处理阶段执行,if语句是C代码,在程序运行的时候执行

2)if语句无论如何全部会编译成二进制指令

条件编译指令:只会将符合条件的C代码编译为二进制指令

3)实际上,if语句一定程度上可以替换成条件编译指令,但是,条件编译指令的条件不能是变量参与,只能是宏

6.条件编译指令的第三种用法

#ifdef 宏名

C代码

#endif

如果定义了指定的宏,就编译其中的代码,否则就算了

#ifndef 宏名

#endif

如果没有定义指定的宏,就编译其中的代码否则就算了

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/99863
推荐阅读
相关标签
  

闽ICP备14008679号