转义字符![](https://i-blog.csdnimg.cn/blog_migrate/822027a2a709f1681f63d6873cf44c54.jpeg)
\130是8进制,转换成10进制是88,以88作为ASCII码值的字符是X。
1X8X8+3X8+0X1=88
\x30中的30是16进制,转换成10进制是48,以48作为ASCII码值的字符是O。
3X16+0X1=48
举个例子
#include<stdio.h>
#include<string.h>
int main()
{
int len=strlen("C:\test\class111\123.c");//\c虽然不是转义字符,但是被编译器当成转义字符来识别的
printf("%d", len);
return 0;
}结果:17
c语言语句
C语言的代码是由一条一条语句构成的,C语言中的语句分为以下五类:
1、空语句:
#include<stdio.h>
int main(){
;//空语句
return 0;
}
2、表达式语句:
在表达式后边加上分号。
3、函数调用语句:
函数调用的时候,也会加上分号,就是函数调用语句。
printf();//函数调用语句
4、复合语句:
代码块
5、控制语句:
控制语句用于控制程序的执行流程,以实现程序的各种结构方式,它们由特定的语句定义符组成,C语言有9种控制语句,可分为三类:
(1)条件语句:
if语句、switch语句;
switch
switch语句中表达式的类型只能是:整形和枚举类型
case语句后一般放整形结果的常量表达式或者枚举类型,枚举类型也可以看成是一个特殊的常量
没有规定case必须在default之前,一般case最好放在default之前
switch语句中的default子句可以放在任意位置
switch语句中case表达式不要求顺序,但一般还是按照次序来
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
func(a);
scanf_s("%d", &a);
printf("%d", b);
return 0;
}
int func(int a)
{
int b;
switch (a)
{
case 1:b = 30;
case 2:b = 20;
case 3:b = 16;
default:b = 0;
}
return b;}
switch的每个case之后如果没有加break语句,当前case执行结束后,会继续执行紧跟case中的语句。
func(1)可知,在调用func时形参a的值为1,switch(a)<==>switch(1),case 1被命中,因为该switch语句中所有分支下都没有增加break语句,因此会从上往下顺序执行,最后执行default中语句返回。
if
#include <stdio.h>
int main() {
int x = 3;
int y = 3;
switch (x % 2) {
case 1:
switch (y)
{
case 0:
printf("first");
case 1:
printf("second");
break;
default: printf("hello");
}
case 2:
printf("third");
}
return 0;
}
switch的每个case之后如果没有加break语句,当前case执行结束后,会继续执行紧跟case中的语句。
进入的第一个case只是入口。
即:先在内部switch的default位置打印hello,紧接着在外部case2中打印third
#include<stdio.h>
int main()
{
int a = 0, b = 0;
// for循环将a和b的初始值均设置为1
for (a = 1, b = 1; a <= 100; a++)
{
if (b >= 20) break;
if (b % 3 == 1)
{
b = b + 3;
continue;
}
b = b-5;
}
printf("%d\n", a);
return 0;
}
第一次循环:a = 1,b=1--->b小于20,if不成立,b%3==1%3==1成立,b=b+3, 此时b的值为4
第一次循环:a = 2,b=4--->b小于20,if不成立,b%3==4%3==1成立,b=b+3, 此时b的值为7
第一次循环:a = 3,b=7--->b小于20,if不成立,b%3==7%3==1成立,b=b+3, 此时b的值为10
第一次循环:a = 4,b=10--->b小于20,if不成立,b%3==10%3==1成立,b=b+3, 此时b的值为13
第一次循环:a = 5,b=13--->b小于20,if不成立,b%3==13%3==1成立,b=b+3, 此时b的值为16
第一次循环:a = 6,b=16--->b小于20,if不成立,b%3==16%3==1成立,b=b+3, 此时b的值为19
第一次循环:a = 7,b=19--->b小于20,if不成立,b%3==19%3==1成立,b=b+3, 此时b的值为22
第一次循环:a = 8,b=22--->b大于20,if成立,循环break提出
最后打印a:8
验证:
(2)循环语句:
do while语句、while语句、for语句。
注释
一、注释是对代码的说明,编译器会忽略注释。好的适度的注释可以帮我们很好的理解代码。
1、/* 内容 */
2、
/*
内容
*/
3、内容/*.....*/内容
编译时, /*....*/被替换成空格
4、内容
//注释
5、 内容//注释
二、/* */注释可以分行——以/*开始,*/结束的块式注释
// 注释只能单行——此种注释范围从//开始,以换行符\n结束的单行注释
三、
/**/不支持嵌套使用
四、双引号里面的注释符号,被视为普通字符,没有注释作用。
在字符串里的//和/*都不作为注释的开始。而是作为字符串的一部分。
C语言的数据类型和变量
数据类型
整数变量声明为unsigned的好处是,同样长度的内存能够表示的最大整数值,增大了一倍。
1、字符型 char
[signed] char 取值范围;-128~127
unsigned char 取值范围;0~255
2、整型
(1)整型 int=[signed] int
unsigned [int]
(2)短整型 short [int]
[signed] short [int] 取值范围;-32768~32767
unsigned short [int] 取值范围;0~65535
(3)长整型 long [int]
[signed] long [int]
unsigned long [int]
(3)长长整型 long long [int]
[signed] long long [int]
unsignedlong long [int]
3、浮点型
单精度浮点型 float
双精度浮点型 double
long double
4、布尔类型 _Bool
0表示假,非0表示真。
布尔类型的使用得包含头文件<stdbool.h>
布尔类型变量的取值是:true或者false
创建变量
变量创建的本质是在内存上开辟空间。
int a=10;要向内存申请4个字节的空间,存放数据10
1、数据类型+变量名
2、变量在创建的时候就给一个初始值,就叫初始化。
3、变量的分类
全局变量:在大括号外部定义的变量就是全局变量
全局变量的使用范围更广,整个工程中想使用,都是有办法使用的。
局部变量。在大括号内部定义的变量就是局部变量
局部变量的使用范围是比较局限,只能在自己所在的局部范围内使用的。
遵循就近原则:当局部变量和全局变量同名的时候,局部变量优先。
例子:
#include<stdio.h>
int a=2023;//全局变量
int main()
{
{
int a=2022;//局部变量
printf("%d\n",a);//局部优先
}
printf("%d\n",a);//全局优先
return 0;
}
结果:
2022
2023
4、全局变量和局部变量在内存中存储在哪里呢?
一般我们在学习C/G++语言的时候,我们会关注内存中的三个区域:栈区、堆区、静态区。
1.局部变量是放在内存的栈区
2.全局变量是放在内存的静态区
3.堆区是用来动态内存管理的
5、复合语句中定义的变量只能在复合语句中使用
原码、反码、补码
原码、反码、补码
这里不涉及小数(浮点数)
整数的2进表示方法有二种,即原码、反码和补码。
三种表示方法均有符号位和数值位两分,符号位都是用0表示“正”,用1表示“负”,
最高位的一位是被当做特号位,剩余的都是数值位。
整型占用4个字节=32个bit(可以参照本目录计算机的常见单位)
有符号的整数——正整数和无符号整数的原、反、补码都相同。int a=1;
原码 00000000 00000000 00000000 00000001
反码 00000000 00000000 00000000 00000001
补码 00000000 00000000 00000000 00000001
有符号的整数——负整数的三种表示方法各不相司,原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码:将原码的符号位不变,其他位依次取反就可以得反码。
补码:反码+1就得到补码,int a=-1;
原码 10000000 00000000 00000000 00000001
反码 11111111 11111111 11111111 11111110
补码 11111111 11111111 11111111 11111111
加法和减法
对于整型来说:数据存放内存中其实存放的是补码,并且倒着存,打印出来的是原码。
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理,
1+(-1)如果用原码计算
1的原码 00000000 00000000 00000000 00000001
-1的原码 10000000 00000000 00000000 00000001
10000000 00000000 00000000 00000010=-2X
1+(-1)如果用补码计算
1的补码 00000000 00000000 00000000 00000001
-1的补码 11111111 11111111 11111111 11111111
1 00000000 00000000 00000000 00000000因为int存不下,所以把1丢了
00000000 00000000 00000000 00000000=0\/
同时,加法和减法也可以统一处理(CPU只有加法器,有了补码就可以算减法),
此外,补码和反码相互转换,其运算过程是相同的,不需要额外的硬件电路。
原码 取反 +1 到补码
补码 取反 +1 到原码
补码 -1 取反 到原码
原码 10000000 00000000 00000000 00000001
取反 11111111 11111111 11111111 11111110+1 111111111 11111111 11111111 11111111
取反 10000000 00000000 00000000 00000000
+1 10000000 00000000 00000000 00000001
操作符
算数操作符
+、 -、 * 、 / 、 %、(双目操作符)
除法/
上图中m得到2,原因在于C语言里面的整数除法是整除,只会返回整数部分,丢弃小数部分。下图可证明。
如果希望得到小数部分的结果,两个运算数必须至少有一个是浮点数,这时C语言就会进行浮点数除法。
运算符%
1、只能用于整数之间
2、
赋值操作符
一、赋值
在变量创建的时候给一个初始值叫初始化,在变量创建好后,再给一个值,这叫赋值。
#include<stdio.h>
int main()
{
int a=1;//初始化
a=2;//赋值
return 0;
}
二、从右向左,连续赋值
int a=1;
int b=2;
int c=3;
b=a+3;//拆开写
c=b;
复合赋值符
+=、-=、*=、/=、%=、>>=、<<=、&=、|=、^=
单目操作符
++、--、+(正)、-(负)
自增、减操作符:a++、++a、a--、--a、
a=a+1;
a+=1;
a++;
++a;
自增:三个作用相同
a前置和后置的区别,放表达式中观察:
b=++a;分为两步:a=a+1;b=a;
a后置计算口诀:先++,后使用
b=a++;分为两步:b=a;a=a+1;
a前置计算口诀:先使用,后++
移位操作符
移位操作符的操作数只能是整数,不要移动负数位,这个是标准未定义的
<<左移操作符
左边丢弃,右边补0
>>右移操作符
逻辑右移:右边丢弃,左边补0
大部分采用 算术右移:右边丢弃,左边补符号位
-1的补码 11111111 11111111 11111111 11111111
算术右移 1 11111111 11111111 11111111 1111111
逻辑右移 0 11111111 11111111 11111111 1111111
位操作符
位操作符的操作数只能是整数,
按位与&
两个都为1才为1
用法
判断a的二进制位的补码的最后一位
#include<stdio.h>
int main()
{
int a = 21;
int b = 0;
b = a & 1;
if (b == 1) printf("a的补码第一位是1");
else printf("a的补码第一位是0");
//21的补码00000000000000000000000000010101
//1的补码 00000000000000000000000000000001
// a&1 00000000000000000000000000000001
return 0;
}
#include<stdio.h>
int main()
{
int a = 21;
int b = 0;
b =( a>>4) & 1;
if (b == 1) printf("a的补码第五位是1");
else printf("a的补码第五位是0");//21的补码 00000000000000000000000000010101
//21>>4 0000 0000000000000000000000000001
}
按位或|
有一个为1就为1
应用
将a的二进制中第五位改成1
#include<stdio.h>
int main()
{
int a = 13;
a = (1 << 4) | a;
printf("%d", a);
//00000000 00000000 00000000 00001101
//00000000 00000000 00000000 00010000
return 0;
}
按位异或^
对应的二进制位上相同为0
按位取反操作符~ —>
应用
二进制位是0的变1,1的变0
#include<stdio.h>
int main()
{
int n = 0;
int a = ~n;
printf("%d\n",a);
//00000000 00000000 00000000 00000000 0的补码
// 11111111 11111111 11111111 11111111 取反后的补码
//10000000 00000000 00000000 00000000
//10000000 00000000 00000000 00000001 原码-1
return 0;
}
练习1:比较a和b的大小
a^a=0
0^a=a
3^3^5=5
3^5^3=5
支持整数交换律
方法一:
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
int temp = 0;
temp = a;
a = b;
b = temp;
printf("a=%d,b=%d", a, b);
return 0;}
方法二:
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
int c=a + b;
a = c - a;
b = c - a;
printf("a=%d,b=%d", a, b);
return 0;
}
方法三:不创建临时变量,实现两个数的交换
#include<stdio.h>
int main(
{
int a = 3;
int b = 5;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a=%d,b=%d", a, b);
return 0;
}
练习2:求一个整数存储在内存中的二进制中1的个数。
#include<stdio.h>
int main()
{
int a = 15;
int count = 0;
while (a)
{
if (a % 2 == 1)
{
count++;
}
a = a / 2;
}
printf("count=%d\n", count);
return 0;
}
负数不适用
负数%2==负数
负数/2==0
退出循环
#include<stdio.h>
int main()
{
int a = 0;
int count = 0;
scanf_s("%d", &a);
int i = 0;
for (i = 0; i < 32; i++)
{
if (((a >> i) & 1) == 1)
count++;
}
printf("count=%d\n", count);
return 0;
}//00000000 00000000 00000000 00001111 15的原码、反码、补码
//10000000 00000000 00000000 00000001 -1的原码
//11111111 11111111 11111111 11111110 -1的反码
//11111111 11111111 11111111 11111111 -1的补码
负数也适用
n = n & (n - 1);
n = 13;
1101-n
1100-n-1
1100-n&(n-1)1100-n
1011-n-1
1000-n(n-1)
n的二进制序列中,最右边的1就消失了#include<stdio.h>
int main()
{
int a = 0;
int count = 0;
scanf_s("%d", &a);
while (n)
{
n = n & (n - 1);
count++;
}
printf("count=%d\n", count);
return 0;
}
负数也适用
练习3:判断一个数是否是2^n次方
#include<stdio.h>
int main()
{
int n = 0;
scanf_s("%d", &n);
if((n & (n - 1)) == 0)
{
printf("yes\n");
}
else printf("no\n");
return 0;
}
逗号操作符
优先级最低
逗号表达式:用逗号隔开的多个表达式。
从左到右依次执行。整个表达式的结果是最后一个表达式的结果。
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, b = a + 1);
printf("%d\n", c);
return 0;
}
//13
改造后
while(a=test1(),test2(a),a>0)
改造前
while(a>0)
{
a=test();
test2(a);
}
下标访问[]
操作数:一个数组名+一个索引值 (下标)
#include<stdio.h>
int main()
{
int a[3] = { 1,2,3 };
printf("%d\n", a[2]);//[]下标引用操作符//[]的操作数a,2
return 0;
}
函数调用()
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的 参数。
#include<stdio.h>
int main()
{
int ret=Add(2, 3);//函数调用操作符()//()的操作数:Add,2,3
printf("%d\n", ret);
return 0;
}
int Add(int x, int y)//函数定义
{
return x + y;
}
操作符的属性:优先级、结合性
优先级相同,看结合性
优先级 | 运算符 | 描述 | 结合性 |
1 | ++ - - () [] 。 -> (type){list} | 后缀自增与自减 函数调用 数组下标 结构体与联合体成员访问 结构体与联合体成员通过指针访问 复合字面量(C99) | 从左到右 |
2 | ++ - - + - !~ (type) * & sizeof _Alignof | 前缀自增与自减 一元加与减 逻辑非与逐位非 转型 间接访问(解引用) 取地址 取大小 对齐要求 | 从右到左 |
3 | * / % | 乘法、除法及余数 | 从左到右 |
4 | + - | 加法及减法 | 从左到右 |
5 | << >> | 逐位左移及右移 | 从左到右 |
6 | < <= > >= | 分别为小于与小于等于的关系运算符 分别为大于与大于等于的关系运算符 | 从左到右 |
7 | == != | 分别为等于与不等于关系 | 从左到右 |
8 | & | 逐位与 | 从左到右 |
9 | ^ | 逐位异或 | 从左到右 |
10 | | | 逐位或 | 从左到右 |
11 | && | 逻辑与 | 从左到右 |
12 | || | 逻辑或 | 从左到右 |
13 | ?: | 三元条件 | 从右到左 |
14 | = += -= *= /= %= <<= >>= &= ^= |= | 简单赋值 以和及差赋值 以积、商及余数赋值 以逐位左移及右移赋值 以逐位与、异或及或赋值 | 从右到左 |
15 | , | 逗号 | 从左到右 |
表达式求值
表达式求值之前要进行类型转换,当表达式中的值转换到适当的类型,才开始计算。
整型提升
C语言中整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。 char short int/unsigned int
规则:
1、有符号整数提升是按照变量的数据类型的符号位来提升的
有符号的整数,1个符号位和31个数值位
2、无符号整数提升,高位补0
无符号的整数,没有符号位,32位都是数值位
算数转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的类型转换成另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
1、long double
2、double
3、float
4、unsigned long int
5、long int
6、unsigned int
7、int
从下到上
表达式求值
强制类型转换
int a=(double)3.14;//强制类型转换
printf
基本用法
printf()的作用是将参数文本输出到屏幕。它名字里面的f代表 format (格式化),表示可以定制输出文本的格式。按照指定的格式打印数据。
printf()不会在行尾自动添加换行符,运行结束后,光标就停留在输出结束的地方,不会自动换行。
为了让光标移到下一行的开头,可以在输出文本的末尾,添加一个换行符\n。
printf()是在标准库的头文件stdio.h定义的。使用这个函数之前,必须在源码文件头部引入这个头问文件。
占位符
printf()可以在输出文本中指定占位符。
所谓“占位符”,就是这个位置可以用其他值代入。
上面示例中,love you %d days\n是输出文本,里面的%d就是占位符,表示这个位置要用其他值来替换。
占位符的第一个字符一律是%,第二个字符表示占位符的类型,%d表示这里代入的值必须是一个整数。
printf()的第二个参数就是替换占位符的值,上面的例子是整数1314替换%d,执行后的输出结果就是lova you 1314。
常见的占位符除了%d,还有%s表示代入的是字符串。
输出文本里面可以使用多个占位符。
printf()参数与占位符是一一对应关系,如果有n个占位符,printf()的参数就应该有n+1个。如果参数个数少于对应的占位符,printf()可能会输出内存中的任意值。
输出格式
printf()可以定制占位符的输出格式。
printf(%5d)允许限定占位符的最小宽度。
上面示例中,5d表示这个占位符的宽宝至少为5位。如果不满5位,对应的值的前面会添加空格。
输出的值默认是右对齐,即输出内前面会有空格;如果希望改成左对齐,在输出内容后面添加空格,可以在占位符的%的后面播入一个-号。
对于小数,这个限定符会限制所有数字的最小宽度。
小数点默认打印6位。
限定小数位数
输出小数时,有时希望限定小数的位数。举例来说,希望小数点后面只保留两位,占位符可以写成%.2f。
最小宽度和小数位数这两个限定值,都可以用*代替,如下:
%.数字s限定输出部分字符串。
占位符列举
printf()的占位符有许多种类,与c语言的数据类型相对应。下面列出常用的占位符。
%a:16进制浮点数,字母输出为小写。
%A:16进制浮点数,字母输出为大写。
%c:字符。除了%c外,都会自动忽略起首的空白字符,返回当前第一个字符,无论该字符是否为空格,如果要强制跳过字符前的空白字符,可以写成" %c",即%c前加上一个空格,表示跳过0个或者多个空白字符。
%d:10进制整数。
%i:整数,基本等同于%d。
%e:使用科学计数法的浮点数,指数部分的e为小写。
%E:使用科学计数法的浮点数,指数部分的E为大写。
%f:小数(包含float类型和double类型)。
%g:6个有效数字的浮点数。整数部分一日超过6位,就会自动转为科学计数法,指数部分的e
为小写。
%G: 等同于%g,唯一的区别是指数部分的E为大写。
%hd: 十进制short int类型。
%ho: 八进制short int类型,
%hx: 十六进制short int类型。
%hu: unsigned short int类型
%ld: 十进制long int类型。
%lo: 八进制long int类型。
%lx: 十六进制long int类型。
%lu: unsigned long int类型。
%s:从当前第一个非空白字符开始读起,直到遇到空白字符(即表格、换行符、制表符等)为止。因为%s不会包含空白字符,所以无法用来读取多个单词,除非多个%s一起使用。scanf()遇到%s占位符,会在字符串变量末尾存储一个空字符\0。scanf()将字符串读入字符数组时,不会检测字符串是否超过了数组长度。所以,存储数组时,很可能会超过数组的边界,导致意想不到的结果。为了防止这个结果发生,使用%s占位符时,应该指定读入字符串的最长长度,多余的字符串会被丢弃。 %.数字s限定输出部分字符串。
%zd:size_t类型。
%%: 输出一个百分号。
scanf
'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
方法一:scanf_s
scanf_s函数是VS这个IDE提供的,不是C语言标准库中的函数。
那就意味着你在你代码中使用了scanf_s函数。你的代码在其他的编译器上是不能通过编译的,使得代码就降低了可移植性。
#include<stdio.h>
int main()
{
int n;
scanf_s("%d", &n);
printf("%d", n);
return 0;
}
方法二:#define _CRT_SECURE_NO_WARNINGS放开头
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int n;
scanf("%d", &n);
printf("%d", n);
return 0;
}
方法三:#pragma warning(disable:4996)放其他地方也可以
#pragma warning(disable:4996)
#include<stdio.h>
int main()
{
int n;
scanf("%d", &n);
printf("%d", n);
return 0;
}
*赋值忽略符,只要把*加在任何占位符的百分号后面,该占位符就不会返回值,解析后将被丢弃。
scanf()时 数组和指针变量不需要&。
数组
数组是一组相同类型元素的集合。
数组分为一维数组和多维数组。
把一维数组做为数组元素的数组就是二维数组,二维数组作为数组元素的数组被称为三维数组,二维数组以上的数组统称为多维数组。
数组创建
一维数组 二维数组
type arr_name[常量值] type arr_name[常量值1][常量值2]
数组中存放数据的类型 数组名[数组的大小]
#include<stdio.h> #include<stdio.h>
int main() int main()
{ {
int math[3]; int math[3][4];
return 0; return 0;
} }
数组的初始化
创建数组的时候,需要给定一些初始值,这种就称为初始化。
#include<stdio.h>
int main()
{
int math[3] = { 1,2,3 };//一维数组
int math[3][4] = { {1,2,3 ,4},{5,6,7,8},{9,10,11,12} };//二维数组//先放满一行
//行可以省略,列不行
int math[3] = { 1,2,3 };//完全初始化
int math1[3] = { 1,2 };//不完全初始化,剩余的元素初始化为0
int math2[] = { 1,2,3 };//数组的大小,是编译器根据初始化的内容确定的
return 0;
}
数组的类型
数组算是一种自定义类型,去掉数组名就是数组的类型。
#include<stdio.h>
int main()
{
int math[3] = { 1,2,3 };//math的类型是int [3]
int math1[3] = { 1,2 };//math1的类型是int [3]
return 0;
}
数组下标
C语言规定数组是有下标的,从0开始,假设数组有n个元素,最后一个元素的下标是n-1,下标就相当于数组元素的编号,如下:
int arr[10]={1,2,3,4,5,6,7,8,9,10};
下标0~9
二维数组的行和列都是从0开始的。
下标引用操作符
在C语言中数组的访问提出了一个操作符[],这个操作符叫:下标引用操作符。
有了下标访问操作符,我们就可以轻松的访问到数组的元素了,比如我们访问下标为7的元素,我
们就可以使用arr[7],要访问下标是3的元素,就可以使用arr[3]。
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n%d\n", arr[7], arr[3]);//下标访问操作符
return 0;
}
#include<stdio.h>
int main()
{
int a[3][2]={1,2,3,4,5,6};
printf("%d\n",a[1][2]);
return 0;
}
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
#include<stdio.h>
int main()
{
int arr[3][5] = { {1,2,3,4,5},{5,6,7,8,9},{1,2,34,4,5} };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
C99
#include<stdio.h>
int main()
{
int n = 0;
scanf_s("%d", &n);
int arr[n];
return 0;
}
C99之前数组的大小只能是常量、常量表达式指定,不能使用变量
C99之后为什么就是用了变量?变长数组C99中,引入了变长数组的概念,允许数组的大小是变量的。
VS虽然支持了C99的语法,但是不是全部支持,变长数组在VS上是不支持的。
变长数组不能初始化的。
数组大小可以指定。
数组的输入
#include<stdio.h>
int main()
{
int a[10];
int i = 0;
for (i = 0; i < 10; i++)
{
scanf_s("%d", &a[i]);
}
for (i = 0; i < 10; i++)
{
printf("%d\n", a[i]);
}
return 0;
}
#include<stdio.h>
int main()
{
int arr[2][5];
int i = 0;
for (i = 0; i < 2; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
scanf_s("%d", &arr[i][j]);
}
}
printf("\n");
for (i = 0; i < 2; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
一维数组在内存中的存储
#include<stdio.h>
int main()
{
int num[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
for (i = 0; i < 10; i++)
{
printf("num[%d]=%p\n", i,&num[i]);//%p用来打印地址的
}
return 0;
}
10进制
0 1 2 3 4 5 6 7 8 9
2进制
0 1
8进制
0 1 2 3 4 5 6 7
16进制
0 1 2 3 4 5 6 7 8 9 a b c d e f
10 11 12 13 14 15
#include<stdio.h>
int main()
{
int num[3][3] = { 1,2,3,4,5,6,7,8,9};
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 3; j++)
{
printf("num[%d][%d]=%p\n", i,j, &num[i][j]);//%p用来打印地址的
}
}
return 0;
}
1、数组在内存中是连续存放的
2、随着下标的增长,地址是由低到高变化的
sizeof
sizeof中C语言是一个关键字,是可以计算类型或者变量大小的,其实sizeof也可以计算数组的大小。
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3 };//10*4=40
printf("%d\n", sizeof(arr));//sizeof(数组名)计算的是数组的所占内存空间的大小
printf("%d\n", sizeof(arr[0]));
printf("%d\n", sizeof(arr)/sizeof(arr[0]));//计算的是数组的元素个数
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
strlen
c语言中的strlen函数用于计算一个字符串中元素的个数,即从数组头部计数,直到遇到字符串结束符'\0'为止,计数结果不包括'\0'。
sizeof和strlen
1.sizeof:在程序中计算字符串时把末尾'\0'计入在内。
2.strlen:是计算字符串的有效长度,不把末尾'\0'计入在内,直接是测量字符串长度。
函数的概念
C语言中的函数就是一个完成某项特定的任务的一小段代码。
这段代码是有特殊的写法和调用方法的。
C语言的程序其实是由无数个小的函数组合而成的,也可以说:一个大的计算任务可以分解成若干个较小的函数(对应较小的任务)完成。同时一个函数如果能完成某项特定任务的话,这个函数也是可以复用的,提升了开发软件的效率。
库函数和自定义函数
函数设计应该追求高内聚低耦合
高内聚低耦合即:函数体内部实现修改了,尽量不要对外部产生影响,否则:代码不方便维护
库函数
C语言标准中规定了C语言的各种语法规则,C语言并不提供库函数;C语言的国际标准ANSI C现定了一些常用的国数的标准,被称为标准库,那不同的编译器厂商根据ASI提供的C语言标准就给出了一系列函数的实现。这些函数就被称为库函数。
我们前面内容中学到的printf、scanf都是库函数,库函数的也是函数,不过这些函数已经是现成的,我们只要学会就能直接使用了。有了库函数,一些常见的功能就不需要程序员自己实了,一定程度提升了效率:同时库函数的质量和执行效率上都更有保证。
各种编译器的标准库中供了一系列的库函数,这些库函数根据功能的划分,都在不同的头文件中进行了声明。
自定义函数
ret_type fun_name(形式参数)
{
}
ret type是函数返回类型
fun_name是函数名
括号中放的是形式参数
{}括起来的是函数体
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf_s("%d %d", &a, &b);
int c = add(a, b);
printf("%d\n", c);
return 0;
}
int add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
简化:
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf_s("%d %d", &a, &b);
int c = add(a, b);
printf("%d\n", c);
return 0;
}
int add(int x, int y)
{
return (x+y);
}
形参和实参
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf_s("%d %d", &a, &b);
int c = add(a, b);//a和b是真实传递给add的参数,是实际参数,简称实参
printf("%d\n", c);
return 0;
}
int add(int x, int y)//形式上的参数,简称形参
{
return (x+y);
}
形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形式的实例化。
我们在调试的时候可以观察到,x和y确实得到了a和b的值,但是x和y的地址和a和b的地址是不一样的,所以我们可以理解为形参是实参的一份临时拷贝。
exec((vl,v2),(v3,v4),v5,v6) 总共有四个参数
(v1, v2)属于第一个实参,逗号表达式,真实的参数是v2
(v3,v4)属于第二个实参,逗号表达式,真实的参数是v4
v5属于第三个实参
v6属于第四个实参
return语句
一、return+数值;
return+表达式;
return;
函数中需要提前结束,但是函数的返回类型是void,不需要返回任何值的时候,就使用return;
二、return的返回值和函数返回类型不一致,系统会自动将返回的值隐式转换为函数的返回类型。
#include<stdio.h>
int main()
{
int n = test();
printf("%d", n);
return 0;
}
int test()
{
return 3.14;
}
3
三、函数中可以没return语句,只有需要返回值,或者提前结束函数的是才使用retutrn。
return语句执行后,函数就彻底返回,后边的代码不再执行。
如果函数中存在if等分支的语句,则要保证每种情况下都有return返回,否则会出现编译错误。不是所有的路径都有返回值。
#include<stdio.h>
int main()
{
int n = test();
printf("%d", n);
return 0;
}
int test()
{
int m = 0;
if (m == 5)//n=5才有返回
return 1;//else return -1;
}
数组做函数参数
函数的形参要和函数的实参个数匹配
函数的实参是数组,数组大小可以省略不写
形参如果是二维数组,行可以省略,但是列不能省略
数组传参,形参是不会创建新的数组的
形参操作的数组和实参的数组是同一个数组
#include<stdio.h>
int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
int sz=sizeof(arr)/sizeof(arr[0]);
set_arr(arr,sz);
print_arr(arr,sz);
return 0;
}
void set_arr(int arr[],int sz)
{
for(int i=0;i<sz;i++)
{
arr[i]=-1;
}
}
void print_arr(int arr[],int sz)
{
for(int i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
嵌套调用和链式访问
一、嵌套调用
就是函数之间的互相调用。
int is_leap_year(int y)
{
if ((y % 400 == 0) || ((y % 4 == 0) && (y % 100 != 0)))
return 1;
else
return 0;
}
int get_days_of_month(int y, int m)
{
int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int day = days[m];
if (is_leap_year(y) && m == 2)
day += 1;
return day;
}
#include<stdio.h>
int main()
{
int y = 0;
int m = 0;
scanf_s("%d %d", &y, &m);
int d = get_days_of_month(y, m);
printf("%d\n", d);
return 0;
}
二、链式访问
将一个函数的返回值作为另一个函数的参数,像链条一样将函数串起来。
printf的返回是什么
printf函数返回的是打印在屏幕上的字符的个数
解析:
第三个printf打印43,在屏幕上打印2个字符43,再返回2
第二个printf打印2,在屏幕上打印一个字符2,再返回1
第一个printf打印1
所以屏幕上最终打印:4321
函数的声明和定义
先声明后使用
一、单个文件
这种情况下是函数的定义在函数调用后面
这是因为C语言编译器对源代码进行编译的时候,从第一行往下扫描的,当遇到函数调用的时候,并没有发现前面有函数定义,就报出了警告。
方法一:
如果我们将函数的定义放在函数调用的前面
函数的定义也是一种特殊的声明
方法二:
在函数调用之前先声明一下函数
声明函数:函数的返回类型,函数名和函数的参数(只保留类型,省略掉名字也是可以的)
二、多个文件
函数的声明放在头文件(.h)中,函数的定义和主函数放在源文件(.c)中。
全局变量:
大括号外部定义的变量
局部变量:
大括号内部定义的变量
内存
一、栈区
局部变量
函数参数
二、静态区
全局变量
静态变量
三、堆区
C语言中关键字static和extern
static是静态的意思,可以用来:
1、修饰局部变量
这是之前的我
代码1
这是之后的我
代码2
a的作用域没有发生变化,生命周期变长了
对比上面两个代码的效果,理解static修饰局部变量的意义。
代码1的test函数中的局部变量i是每次进入test函数先创建变量(生命周期开始)并赋值为0,然后++,再打印,出函数的时候变量生命周期将要结束(释放内存)。
代码2 中,我们出输出结果看,i的值有累加的效果,其实test函数中的i创建好后,出函数的时候是不会销毁的,重新进入函数也就不会重新创建变量,直接上次累积的数值继续计算。
结论:
static修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,本来一个局部变量是存储在内存的栈区的,但是被static修饰后存储到了静态区。存储在静态区的变量和全局变量是一样,生命周期就和程序的生命周期一样了,只有程序结束,变量才销毁,内存才回收。但是作用域不变的。
使用建议:
未来一个变量出了函数后,等下次进入函数继续使用,就可以使用static修饰。
2、修饰全局变量
extern是用来声明外部符号的。
全局变量是具有外部连接属性的
extern是用来声明外部符号的,如果一个全局的符号在A文件中定义的,在B文件中想使用,就可以使用ertern声明,然后使用。
结论:
一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。本质原因是全局变量默认是具有外部链接属性的,在外部的文件中想使用,只要适当的声明就可以使用;但是全局变量被static修饰之后,static int g_val = 2024;或者static int add(int x,int y);外部链接属性就变成了内部链接属性,只能在自己所在的源文件内部使用了,其他源文件,即使声明了,也是无法正常使用的。
使用建议:
如果一个全局变量,只想在所在的源文件内部使用,不想被其他文件发现,就可以使用static修饰。
3、修饰函数
![](https://i-blog.csdnimg.cn/blog_migrate/bd8d3f3a50705eee7db90ca7935995e3.png)
函数默认也是具有外部链接属性
使用建议:
如果一个函数,只想在所在的源文件内部使用,不想被其他文件发现,就可以使用static修饰。
作用域
作用域是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效(可用)的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
局部变量的作用域是变量所在的局部范围。
全局变量的作用域是整个工程。
生命周期
生命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的一个时间段。
局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
全局变量的生命周期是:整个程序的生命周期。
指针( 补充:计算机中常见的单位)
生活中,每个房间有了房间号,就能提高效率,能快速的找到房间。
如果把上面的例子对照到计算中,又是怎么样泥?
我们知道计算上CPU(中央处理器》在处理数据的时候,需要的数据是在内存中读取的,处埋后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何高效的管理呢?
其实也是把内存划分为一个个的内存单元,
每个内存单元的大小取1个字节。
一个比特位可以存储一个2进制的位1或者0。
其中,每个内存单元,相当于一个学生宿舍,一
个人字节空间里面能放8个比特位,就好比同学们
住的八人间,每个人是一个比特位。
每个内存单元也都有一个编号,(这个编号就相当
于宿舍房间的门牌号),有了这个内存单元的编
号,CPU就可以快速找到一个内存空间。
生活中我们把门牌号也叫地址,在计算机中我们
把内存单元的编号也称为地址。C语言中给地址起
了新的名字叫:指针。
所以我们可以解为:
内存单元的编号==地址==指针
指针变量就是用来存放地址的。
存放在指针变量中的值,都会被当作地址使用。
指针变量的大小
#include<stdio.h>
int main()
{
int num = 10;
int* p = #
char ch = 'w';
char* pc = &ch;
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(pc));
return 0;
}4
4
习题
一、多个字符从两端移动,向中间汇聚。
#include<stdio.h>
#include<windows.h>
int main()
{
char arr1[] = "welcome to bit!!!!!!!!!";
char arr2[] = "***********************";
int left = 0;
int right = strlen(arr1)-1;
printf("%s\n", arr2);
while(left<=right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
Sleep(1000);//休眠,单位毫秒
system("cls");//用来执行系统命令的
left++;
right--;
}
printf("%s\n", arr2);
return 0;
}
二、逆序输出
#include <stdio.h> int main() { int arr[10] = {0}; for(int i=0; i<10; i++) { scanf_s("%d", &arr[i]); } for(i=9; i>=0; i--) { printf("%d ", arr[i]); } return 0; }
三、有序数列合并
描述
输入两个升序排列的序列,将两个序列合并为一个有序序列并输出。
数据范围: 1 \le n, m \le 1000 \1≤n,m≤1000 , 序列中的值满足 0 \le val \le 30000 \0≤val≤30000
输入描述:
输入包含三行,
第一行包含两个正整数n, m,用空格分隔。n表示第二行第一个升序序列中数字的个数,m表示第三行第二个升序序列中数字的个数。
第二行包含n个整数,用空格分隔。
第三行包含m个整数,用空格分隔。
输出描述:
输出为一行,输出长度为n+m的升序序列,即长度为n的升序序列和长度为m的升序序列中的元素重新进行升序序列排列合并。
#include <stdio.h> int main() { int n = 0; int m = 0; int arr1[1000] = {0}; int arr2[1000] = {0}; scanf("%d %d", &n, &m); for(int i=0; i<n; i++) { scanf("%d", &arr1[i]); } for(int i=0; i<m; i++) { scanf("%d", &arr2[i]); } int j = 0; int i = 0; while(i<n && j<m) { if(arr1[i] < arr2[j]) { printf("%d ", arr1[i]); i++; } else { printf("%d ", arr2[j]); j++; } } if(i == n) { for(; j<m; j++) { printf("%d ", arr2[j]); } } else { for(; i<n; i++) { printf("%d ", arr1[i]); } } return 0; }
四、空心正方形
打印用“*”组成的“空心”正方形图案。
输入描述:
多组输入,一个整数(3~20),表示输出的行数,也表示组成正方形边的“*”的数量。
输出描述:
针对每行输入,输出用“*”组成的“空心”正方形,每个“*”后面有一个空格。
#include <stdio.h> int main() { int n = 0; while(scanf_s("%d", &n) != EOF) { for(int i=0; i<n; i++) { for(int j=0; j<n; j++) { if((i==0) ||( i==n-1)||(j==0)||(j==n-1)) printf("* "); else printf(" "); } printf("\n");//输一层就换行 } } return 0; }
五、X形图案
打印用“*”组成的X形图案。
输入描述:
多组输入,一个整数(2~20),表示输出的行数,也表示组成“X”的反斜线和正斜线的长度。
输出描述:
针对每行输入,输出用“*”组成的X形图案。
#include<stdio.h>
int main()
{
int n = 0;
while (scanf_s("%d", &n) != EOF)
{
int i = 0;
int j = 0;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if ((i == j) || (i + j == n-1))
printf("*");
else printf(" ");
}
printf("\n");
}
return 0;
}
六、判断三角形
已经给出的三条边a,b,c能否构成三角形,如果能构成三角形,判断三角形的类型(等边三角形、等腰三角形或普通三角形)。
输入描述:
题目有多组输入数据,每一行输入三个a,b,c(0<a,b,c<1000),作为三角形的三个边,用空格分隔。
输出描述:
针对每组输入数据,输出占一行,如果能构成三角形,等边三角形则输出“Equilateral triangle!”,等腰三角形则输出“Isosceles triangle!”,其余的三角形则输出“Ordinary triangle!”,反之输出“Not a triangle!”。
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
int c = 0;
while (scanf_s("%d %d %d", &a, &b, &c) != EOF)
{
if ((a + b) > c && (a + c) > b && (b + c) > a)
{
if (a == b && b == c)
printf("Equilateral triangle!\n");
else if (a == b || a == c || b == c)
printf("lsosceles triangle!\n");
else printf("Ordinary triangle!\n");
}
else printf("Not a triangle!\n");
}
return 0;
}
七、乘法口诀表
#include<stdio.h>
int main()
{
int i = 0;
int j = 0;
for (i = 1; i < 9; i++)
{
for (j = 1; j <= i; j++)
{
printf("%d*%d=%d ", j, i,j*i);
if (i == j)printf("\n");
}
}
return 0;
}
八、求输入的10个整数中的最大值
#include<stdio.h>
int main()
{
int a[10] = {0 };
int max = 0;
int i = 0;
for (i = 0; i < 10; i++)
{
scanf_s("%d", &a[i]);
}for (i = 0; i < 10; i++)
{
if (a[i] > max)
max = a[i];
}
printf("%d", max);
return 0;
}
九、分数求和
计算1/1-1/2+1/3-1/4+1/5 …… + 1/99 - 1/100 的值,打印出结果
#include<stdio.h>
int main()
{
int i = 0;
double sum = 0;
int f = 1;
for (i = 1; i <= 100; i++)
{
sum = sum + 1.0 / i * f;
f = -f;
}
printf("%f", sum);
return 0;
}
十、数9的个数
编写程序数一下 1到 100 的所有整数中出现多少个数字9
#include <stdio.h>
int main()
{
int i = 0;
int count = 0;
for (i = 1; i <= 100; i++)
{
if (i % 10 == 9)//通过% 的方式取当前数据的个位,检测个位数据是否为9,如果是,给计数器加1
count++;
if (i / 10 == 9)//通过/的方式取当前数据的十位,检测十位数据是否是9, 如果是,给计数器加1
count++;
}
printf("%d\n", count);
return 0;
}
个位上:9,19,29,39,49,59,69,79,89,99
十位上:90,91,92,93,94,95,96,97,98,99
十一、素数
1、判断一个数是不是素数
素数:即质数,除了1和自己之外,再没有其他的约数,则该数据为素数,具体方式如下
#include<stdio.h>
int main()
{
int count = 0;
for(int i=101; i<=200; i+=2)
{
for(int j=2; j<=sqrt(i); j++)
{
if(i%j == 0)
{
break;
}
}
if(j>sqrt(i))
{
count++;
printf("%d ", i);
}
}
printf("\ncount = %d\n", count);
return 0;
}
2、实现一个函数is_prime,判断一个数是不是素数。利用上面实现的is_prime函数,打印100到200之间的素数。
int is_prime(int n) { for(int i=2; i<=sqrt(n); i++) { if(0 == n%i) { return 0; } } return 1; }
十二、求最大公约数
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
int c = 0;
printf("请输入两个数字:");
scanf_s("%d%d", &a, &b);
c = a % b;
while (c != 0)
{
a = b;
b = c;
c = a % b;
}
printf("最大公约数为%d\n", b);
return 0;
}
十三、判断闰年
#include<stdio.h>
int main()
{
int year = 0;
scanf_s("%d", &year);
runnian(year);
return 0;
}
int runnian(int y)
{
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
printf("%d是闰年\n",y);
else printf("%d是平年",y);
return 0;
十四、计算斐波那契数1 1 2 3 5 8 13 21
#include<stdio.h>
int main()
{
int n = 0;
int m = 0;
scanf_s("%d", &n);
m=f(n);
printf("%d", m);
return 0;
}
int f(int n)
{
if (n <= 2)return 1;
else return f(n - 1) + f(n - 2);
}
十五、递归实现n的k次方
#include<stdio.h>
int main()
{
int n = 0;
int k = 0;
int c=0;
scanf_s("%d %d", &n,&k);
c=Pow(n, k);
printf("%d\n", c);return 0;
}
int Pow(int n, int k)
{
if (k == 0)return 1;
else if (k >= 1)
return n * Pow(n,k-1);
}
十六、计算一个数的每位之和(递归实现)
#include<stdio.h>
int main()
{
int n = 0;
int m = 0;scanf_s("%d", &n);
m=DigitSun(n);
printf("%d\n", m);return 0;
}
int DigitSum(int n)
{
if (n >9)return DigitSum(n / 10) + n % 10;
else return n;
}
十七、求阶乘
#include<stdio.h>
int main()
{
int n = 0;
int m = 1;
scanf_s("%d", &n);
for (int i = 1; i <= n; i++){
m = i * m;
}
printf("%d", m);
return 0;
}
#include<stdio.h>int main()
{
int n = 0;
int m = 0;
scanf_s("%d", &n);
m=test(n);
printf("%d\n", m);
return 0;
}
int test(int n)
{
if (n <= 1)
return n;else
return n * test(n - 1);}
十八、递归方式实现打印一个整数的每一位
#include<stdio.h>
int main()
{
int n = 0;
scanf_s("%d", &n);t(n);
return 0;}
void t(int n){
if (n > 9)
{t(n / 10);
printf("%d ", n % 10);
}
else printf("%d\n", n);
}