一、初识C语言
1.什么是C语言
C语言是一门计算机语言。
1.2什么是计算机语言
计算机语言是人和计算机语言交流的语言,比如C++,JAVA等等。
1.3计算机语言的发展
最早期是二级制语言(只有0和1) 10111110101010100011010100 某个二级制序列有一定含义,多个二级制序列连在一起形成一段代码。 给一个二进制起一个名字 101101 + ADD – 助记符 表示加法就可以用ADD,这种语言称为汇编语言。 后来发明高级语言,一条语言可以相当于多条汇编语言 B语言—>C语言—>C++(高级语言)
2.第一个C语言程序
创建工程—>创建文件—>编写代码 .c 源文件 .h 头文件
#include <stdio.h> //调用库函数
int main() //主函数-程序的入口,有且仅有一个主函数
{ //int是整型的意思,main前面的int表示main函数调用返回一个整型值
printf(hello,world); //在屏幕上输出hello,world 库函数需要调用
return 0; //返回 0
}
第一次运行成功但是一闪而过
首先右击项目
找到属性
在左侧找到链接器中的系统,然后将子系统设置为控制台
3.数据类型
分类 | 类型 | 大小(byte) | 描述 |
---|---|---|---|
数据类型 | char | 1 | 字符数据类型 |
short | 2 | 短整型 | |
int | 4 | 整型 | |
long | 4 | 长整型 | |
long long | 8 | 更长整型 | |
float | 4 | 单精度浮点型 | |
double | 8 | 双精度浮点型 |
举个例子:
#include <stdio.h>
int main()
{
char ch = 'A'; //将字符A存储到ch变量中
printf("%c\n",ch);
rturn 0;
}
由单引号”引起来的叫字符,如第四行。
其中char表示数据类型,ch是创建的变量表示向内存申请一部分空间来存储。
第五行%c表示打印字符格式的数据。
C语言格式字符
%c | 单个字符 |
%d | 十进制整数(int) |
%ld | 十进制整数(long) |
%f | 十进制浮点数(float) |
%lf | 十进制浮点数(double) |
%o | 八进制数 |
%s | 字符串(char) |
%u | 无符号十进制数(DWORD) |
%x | 十六进制数(0x00000) |
4.变量和常量
4.1定义变量
int age = 20; 定义整数型变量
char ch = ‘w’; 定义字符型变量
4.2变量的分类
全局变量和局部变量
#include <stdio.h>
int num2 = 10; //全局变量-定义在{}代码块外面
int main()
{
int num1 = 5; //局部变量-定义在代码块内部
//局部变量和全局变量相同时局部变量优先
return 0;
}
还有一种全局变量是新建一个项目
这时我们发现上面会出现两个项目,然后我们打开新建的项目定义一个新的变量a
然后我们返回原来的项目发现无法直接引用a,这时我们需要用一个关键字extern声明一下才能使用
这时我们打印a输出结果为5
全局变量可以在全部范围内使用
局部变量只能在定义的代码块内使用
定义变量不能随用随定义,要在开始统一定义变量,不然可能报错。
4.3常量的分类
- 字面常量
- const 修饰的常变量
- #define 定义的标识符常量
- 枚举常量
#include <stdio.h>
#define max 10 //定义标识符常量,此时max等于10且不能修改
int main()
{
3.14;
100; //这种直观写出来的值叫做字面常量
const int num = 4; //const赋予变量num常属性,此时num无法修改
printf("%d",num); //num本质还是变量只是具有常属性
num = 8;
printf("%d",num); //此时会报错
return 0;
}
#include <stdio.h>
//枚举常量--->一一列举
//例如性别:男,女
//枚举关键字 enum
enum sex
{
MALE,
FEMALE
};
int main()
{
enum sex s = FEMALE;
printf("%d",MALE); //输出结果为0且不可改变
printf("%d",FEMALE); //输出结果为1且不可改变
return 0;
}
5.输入数据
输入数据使用scanf()函数
#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int num = 0;
scanf("%d%d",&num1,&num2); //&取地址符号
num = num1 + num2;
printf("%d",num);
}
注意
在VS编译器中scanf()函数被认为是不安全的使用会报错,可以在代码最上方添加一段代码让警告失效。
#define _CRT_SECURE_NO_WARNINGS 1
默认添加此段代码
首先找到安装VS编译器的路径找到newc++file.cpp文件
然后编辑文件输入#define _CRT_SECURE_NO_WARNINGS 1 保存,如果不能编辑将文件复制到别的地方在编辑,之后替换原文件。
6.字符串、转义字符、注释
6.1字符串
“hello world”
这种由双引号引起来的一串字符称为字符串字面值,或者简称字符串。
注:字符串的结果标志是一个\0的转义字符,在计算字符长度的时候\0是结束标志,不算做字符串内容。
借用数组打印字符串
#include <stdio.h>
int main()
{
char arr1[ ] = "abc";
char arr2[ ] = {'a','b','c'};
printf("%s\n",arr1);
printf("%s\n",arr2);
return 0;
}
此时输出结果为
使用编译器调试发现arr1有四个元素,arr2只有三个元素
在数组arr2后面加上一个0,那么最后输出结果相同。
实际上字符串”abc”放到数组arr1里面的元素为’a’ ‘b’ ‘c’ ‘\0’末尾默认有一个\0表示字符串的结束标志。arr2数组后面没有结束标志所以继续打印一些随机值。
表中a对应97为ASCII码值
ASCII表
6.2转义字符
“\”加上数字或者字母将会改变其含义
转义字符 | 意义 | ASCLL码值(十进制) |
---|---|---|
\a | 响铃(BEL) | 007 |
\b | 退格(BS) ,将当前位置移到前一列 | 008 |
\f | 换页(FF),将当前位置移到下页开头 | 012 |
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\r | 回车(CR) ,将当前位置移到本行开头 | 013 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
\’ | 代表一个单引号 | 039 |
\” | 代表一个双引号字符 | 034 |
\ | 代表一个反斜线字符”’ | 092 |
\? | 代表一个问号 | 063 |
\0 | 空字符(NUL) | 000 |
\ddd | 1到3位八进制数所代表的任意字符 | 三位八进制 |
\xhh | 十六进制所代表的任意字符 | 十六进制 |
6.4注释
作用
代码中不需要的代码可以直接删除,也可以注释掉。
代码中有些代码比较难懂,可以加一下注释文字。
注释的内容在运行的过程中直接跳过,不影响运行。
单行注释
//注释内容
2.多行注释
注释内容*/
在多行注释中注意不能嵌套
我们在int a之前和printf之后用了一组多行嵌套,但是在int main和}后也用了一组多行注释,这时我们发现有一部分代码没有被注释进去,所以多行代码注意不能嵌套。
7.选择语句
比如说好好学习能考好大学,然后提出问题你是否要好好学习,这就是选择。
在编程中会遇到很多需要选择的问题,因此我们需要用选择语句来解决。
#include <stdio.h>
int main()
{
int a;
printf("你要好好学习吗?\n");
printf("输入1表示是,输入0表示否");
scanf("%d",&a);
if(a==1)
printf("好大学等着你");
else
printf("回家卖地瓜");
return 0;
}
如上if便是一个选择语句,首先提问,然后让用户回答,如果a的值为1,那么输出”好大学等着你”,如果a的值不为1,那么输出”回家卖地瓜”。
8.循环语句
生活中有些事情需要一直做,比如说学习,但是总会有条件跳出循环,比如说考上好大学。(当然只是举个例子,考上好大学就不学习不可能的,生命不止,学习不休,何以解忧,唯有学习)
#include <stdio.h>
int main()
{
int i = 0; //今天是学习的第0天
while(i < 999) //学到999天跳出循环
{
i++;
printf("你学的还不够多\n");
}
printf("恭喜你"); //跳出循环
return 0;
}
如上定义i等于0,当<999时会一直循环代码,只有当i>=999时才会跳出循环,当然也要注意不要写成没有终止的循环代码。
后面还有for,do while循环,后面再说。
9.函数
我们平常计算两个数的求和需要用到+,每次求和时都会需要重新引入,那么我们可以定义一个函数,我们每次只需要引用一个函数就可以计算两个数的和。
#include <stdio.h>
int Add(int x,int y) //定义x和y的类型
{
int z = 0;
z = x + y;
return 0;
}
int main()
{
int a = 10;
int b = 5;
int sum = 0;
sum = Add(a,b);
printf("%d",sum);
}
在上述代码中我们引用Add这个函数,在调用函数的时候,a的值赋给x,b的值赋给y,然后定义z存储x+y的和,最后返回z,这时Add(a,b)的值就为z的值,然后赋给sum,最后sum的值就是a+b。
10.数组
C语言中定义数组是一组相同类型元素的集合。在内存中的存储具有连续性。
定义:
int arr[10] = {1,2,3,4,5,6,7,8,9,10}; //定义一个整数型数组,最多放10个元素
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n",arr[0]);
return 0;
}
如上代码我们定义一个整数型数组,其中含有十个元素,当我们要访问其中一个元素时需要用到下标,数组内元素的下标是从0开始的,也就是第一个元素的下标为0。如上我们打印第一个元素的值,所以输出为1。
我们也可以借助循环来实现依次打印元素里面的值
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
while(i < 10)
{
printf("%d\n",arr[i]);
i++;
}
return 0;
}
11.操作符
算术操作符
+ | 加号 |
---|---|
– | 减号 |
* | 乘号 |
/ | 除号 |
% | 取余 5%2=1 |
移位操作符
>> | 向右移 |
---|---|
<< | 向左移 |
移(二进制)位操作符
例:
#include <stdio.h>
int main()
{
int a = 1;
a << 1; //向左移动一位
return 0;
}
在第四行代码中定义变量存储1,其中int向内存申请四个字节所以二进制存储形式应该如下
00000000000000000000000000000001
其中向左移一位表示上面数据最左边的一位数丢弃,在最右边补上一位变成
00000000000000000000000000000010
移动完之后如果没有重新赋值其本身不变。
位操作符
在C语言中0为假1为真
& | 按位与(二进制位只要有假就保留假,同时为真则为真) |
| | 按位或(二进制位只要有真就保留真,同时为假则为假) |
---|---|
^ | 按位异或(对应的二进制位相同则为0,对应的二进制位相异则为1) |
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = 0;
c=a & b;
printf("%d\n",c);
c=a | b;
printf("%d\n",c);
c=a ^ b:
printf("%d\n",c);
return 0;
}
按位与 | 按位或 | 按位异或 | |
---|---|---|---|
3的二进制 | 0 1 1 | 0 1 1 | 0 1 1 |
5的二进制 | 1 0 1 | 1 0 1 | 1 0 1 |
结果 | 0 0 1 | 1 1 1 | 1 1 0 |
转成十进制 | 1 | 7 | 6 |
赋值操作符
= | 赋值 | |
+= | 加上之后再赋值 | a=a+10 —> a+=10 |
-= | 减去以后再赋值 | a=a-10 —> a-=10 |
*= | 乘完以后赋值 | |
/= | 除完以后赋值 | |
^= | 按位异或后赋值 | |
|= | 按位或后赋值 | |
&= | 按位与后赋值 | |
>>= | 向右移后赋值 | |
<<= | 向左移后赋值 |
单目操作符
! | 逻辑反操作 |
– | 负值 |
+ | 正值 |
& | 取地址 |
sizeof | 操作数的类型长度(以字节为单位) |
~ | 对一个数的二进制按位取反 |
— | 前置,后置– |
++ | 前置,后置++ |
* | 间接访问操作符(解除引用操作符) |
(类型) | 强制类型转化 |
解释
!
C语言中0表示假,非0表示真
#include <stdio.h>
int main()
{
int a = 10;
printf("%d",!a);
return 0;
}
因为a等于10为非零为真,所以逻辑反操作符!将a转化为假(变成0)所以输出结果为0。
sizeof
#include <stdio.h>
int main()
{
int a = 10;
int arr[10]={0};
printf("%d",sizeof(a)); //输出为4,变量可以去括号
printf("%d",sizeof(int)); //输出为4,类型不能去括号
printf("%d",sizeof(arr)); //输出为40,四个整型元素
printf("%d",sizeof(arr)/sizeof(arr[0]));
//计算有多少个元素,总空间/一个元素占用空间大小
return 0;
}
sizeof 计算的是变量/类型所占空间的大小,单位是字节。
~
#include <stdio.h>
int main()
{
int a = 0; //4个字节,32个bit位
int b = ~a; //b是有符号的整型,如果有符号二进制最高位为符号,1为负号
//~按(二进制)位取反
//000000000000000000000000000000000000000000
//111111111111111111111111111111111111111111
printf("%d",b); //打印的是原码
return 0;
}
原码、反码、补码
负数在内存中存储的时候,存储的是二进制的补码
正数的原码、反码、补码相同
负数的原码是直接按照正负二进制序列写出来的,反码是除了符号位取反,补码是反码加一得到的。
在计算机系统中,数值一律用补码来储存。
如上代码第八行,此时取反后存储的为补码,将补码转换成原码后为
100000000000000000000000000000000000000001
第一位为符号所以输出结果为-1
++/–
#include <stdio.h>
int main()
{
int a = 10;
int b = a++; //后置++先赋值后自增1
printf("%d,%d",a,b); //输出结果为11,10
return 0;
}
如果前置++/–先自增/减1,然后再赋值。
(类型)
#include <stdio.h>
int main()
{
int a = (int)3.14;
return 0;
}
在第三行如果没有加(int)会发生警告,因为3.14是浮点型,转化成整型会发生数据丢失,这里加上(int)把3.14强制转化成整型。
单/双/三目操作符区分
a+b; 加号两边有两个操作数所以称为双目操作符
只有一个操作数的称为单目操作符
有三个操作数的称为三目操作符
关系操作符
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
!= | 用于测试“不相等” |
== | 用于测试“相等” |
逻辑操作符
&& | 逻辑与 |
|| | 逻辑或 |
条件操作符
exp1?exp2:exp3
exp意思是表达式,如果表达式1的结果为真,那么表达式2执行,表达式2的结果为整个表达式的结果,如果为假执行表达式3,表达式3的结果为整个表达式的结果。
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = 0;
c = a > b ? a : b;
printf("%d",c);
return 0;
}
其中a>b表达式为假,所以b的值赋给c,所以输出结果为5。
逗号表达式(所有运算符中级别最低)
exp1,exp2,exp3,……expN
整个表达式的值为最后一个表达式的值
如:
a=(3+7,10*3);
a的值为30
a=(a=3,3+3);
a最后的值为6
下标引用、函数调用的结构成员
. | 这个等会说 |
-> | 这个也等会说 |
[ ] | 数组下标引用操作符,下标从0开始 |
( ) | 函数调用操作符 |
例:
int a = 10;
int b = 20;
int sum = add(a,b); 此时add后面的括号为函数调用操作符(函数知识)
C语言符号优先级
12.常见关键字(C语言内置)(不能与变量名冲突)
auto | 自动 |
在C语言中定义局部变量
int a = 10;其作用周期是一个代码块{},当运行完这个代码块时这个局部变量自动销毁,因为前面隐藏一个关键字auto,原代码应为
auto int a = 10;
由于每个局部变量都有所以省略。
break | 循环中停止循环,switch case语句中 |
case | switch case语句中 |
char | 字符类型 |
const | 常量修饰 |
continue | 循环继续 |
default | 默认 |
do | do while循环 |
double | 双精度浮点型 |
else | if else语句 |
enum | 枚举 |
extern | 引入外部符号(详见全局变量,同理应用于函数详见static解析) |
float | 单精度浮点型 |
for | for循环 |
goto | goto语句 |
if | if语句 |
int | 整型 |
long | 长整型 |
register | 寄存器关键字 |
register int a = 10;
建议把a放进寄存器,是否放入由编译器判断决定
在CPU处理数据时,CPU的处理速度是非常快的,所以首先向寄存器中拿取数据,如果寄存器没有再向高速缓冲器拿取,以此类推到内存,然后硬盘。
从硬盘到寄存器运行速度越来越快,相应的储存空间越来越小。
因此当编程中某个元素应用频繁时可以用register建议放进寄存器。
return | 返回 |
short | 短整型 |
signed | 有符号 平常定义int,short等都是有符号的默认signed省略 |
sizeof | 计算大小 |
static | 静态 |
struct | 结构体关键字 |
switch | switch case关键字 |
typedef | 类型定义 |
union | 联合体/共用体 |
unsigned | 无符号的 unsigned int a = 10; |
viod | 空 |
viod | 空 |
volatile | C语言断位 |
while | do while循环 |
typedef 类型定义–类型重定义
unsigned int num = 20; 定义一个无符号整型变量
此时定义类型太长可以用typedef重定义、
typedef unsigned int u_int; 相当于把unsigned in重新定义一个名字u_int
如果放在所有函数之外,它的作用域就是从它定义开始直到文件尾;
如果放在某个函数内,定义域就是从定义开始直到该函数结尾。
static 静态
修饰局部变量时
#include <stdio.h>
void test()
{
int a = 1;
a++;
printf("a=%d\n",a);
}
int main()
{
int i = 0;
while(i < 5)
{
test();
i++;
}
return 0;
}
上述代码中首先从第10行开始,先定义i=0,然后进入while循环,引入test()函数。
在test函数里面定义a=1,然后a自增1,再然后打印a的值。之后返回主函数i自增1,由于i<5,i每次自增1,所以循环执行五次,之后的每次循环a由于在函数周期结束后自动销毁,所以每次进入test函数后a都要重新定义赋值1,因此最后输出结果为
这时如果我们在test函数定义a前面加上static变成
static int a = 0;
此时输出结果为
这说明a没有被销毁,并且继承了原来的值,跳过了test函数里面对a的定义和赋值。
所以static修饰局部变量时,使局部变量的生命周期变长。
修饰全局变量时
当static修饰第二种全局变量时(从另一个源文件定义变量,然后用extern声明使用),从原来的源文件无法使用另一个源文件定义的变量了
所以static修饰全局变量时,改变了变量的作用域,让静态的全局变量只能在自己所在的源文件内部使用。
修饰函数时
当修饰函数时和全局变量类似
如图在第一个源文件内输入以下代码
(注意extern声明函数的格式)
第二个源文件内输入函数代码
这时我们能正常运行输出a+b的结果为8
此时如果我们把第二个源文件中定义函数前面加上static修饰
int add(int x,int y)改成
static int add(int x,int y)
这时我们再运行第一个源文件时会报错,这是因为static修饰函数改变了函数的链接属性(函数具有外部属性,只要在其他源文件内声明就可以应用函数),使函数外部链接属性变为内部链接属性,此时函数只能在定义函数本身的源文件使用。
#define定义常量和宏
定义常量详见常量
定义宏
前面我们学习函数时可以用函数解决比较大小的问题,同样我们可以用宏解决。
#include <stdio.h>
#define max(x,y) (x>y?x:y)
int main()
{
int a = 10;
int b = 15;
int c = max(a,b);
printf("%d",c);
return 0;
}
在第7行我们定义一个函数max想要求a和b的最大值,但是第二行我们定义了宏用一个条件操作符代替了max从而计算a和b的最大值。
上面的#define是用后者替换前者,其中里面a的值赋给x,b的值赋给y。
13.指针
内存
常见电脑有32位和64位,以32位为例,32位相当于有32根地址线/数据线。32位比特共有2^32个编号,也就是这么多个地址,每个地址都有相应的空间,如果每个空间只有1bit那么这个内存便是0.5g(空间大小乘地址数)。
假设一个32位电脑,一个空间为一个字节。那么定义
int a = 10; 需要申请四个字节也就是要申请四个空间
#include <stdio.h>
int main()
{
int a = 10;
&a; //取地址
printf("%p\n",&a); //打印a的地址
return 0;
}
这时候我们会得到一个十六进制的数字,将十六进制转化成二进制就能得到地址。
同样地址也能存到变量中,这种变量称为地址变量。
int*p = &a; //将整型a的地址存到p中
#include <stdio.h>
int main()
{
int a = 10;
int*p = &a;
*p = 20; //*-解引用操作符
printf("%d\n",a);
return 0;
}
当我们运行上面代码时输出结果为20。
从第四行开始,我们首先创建一个变量a的值为10,然后我们创建一个地址变量p存储a的地址。然后第六行我们解引用操作符解析a的地址,相当于把20放到a的地址中,此时a的值就变为了20。
指针变量的大小
我们定义一个指针变量存储地址,此时指针变量也需要一个内存空间存储。指针变量是存储某个地址,在32为电脑中一个地址就是一个32位的二进制数,也就是4个字节,所以32位电脑一个指针变量的大小为4个字节。同理64位电脑一个指针变量大小为8个字节。
14.结构体
当我们描述一本书时,我们很难用C语言的数据类型描述,我们可以用struct结构体自己定义一个数据类型。
#include <stdio.h>
struct book
{
char name[20]; //定义名字
short price; //定义价格
}; //注意结构体后面有个分号
int main()
{
struct book b1 = {"C语言",20}; //定义的新的数据类型定义变量
printf("书名:%s\n",b1.name);
printf("价格:%d\n",b1.price);
b1.price = 15;
printf("修改后价格:%d\n",b1.price);
return 0;
}
在第10行我们打印b1.name中的.为结构体成员运算符,表示引出b1结构内的成员。
输出结果为
我们可以看到可以用.号来运算结构体内的成员,并且可以修改其中的值。
当我们要把b1存到指针变量里面是我们需要指向struct book
struct book*pb = &b1;
当我们要用指针来打印书名和价格时可以
printf(“%s\n”,(*pb).name); //*pb就指向b1
printf(“%d\n”,(*pb).price);
同样我们也可以用->(指向结构体成员运算符)来指向结构体内部的成员
printf(“%s\n”,*pb->name);
printf(“%d\n”,*pb->price);
.应用到结构体变量来找到成员
->应用到结构体指针来找到成员
上面我们演示如何改变书的价格,但是如果我们按照相同的办法改变书名结果会显示报错,因为书的价格是一个变量,而书的名字是一个数组。数组的的本质是一个地址,如果我们想要改变书的名字那么需要用到strcpy函数。
strcy(地址,数值) 字符串拷贝函数,属于库函数,需要引用头文件string.h
strcpy(b1.name,”C++”);
我们添加这一段代码以后,表示将字符串C++拷贝到此地址中。然后我们就可以改变书名。