目录
1. 基本数据类型
- char_字符型
- short_短整型
- int _整型
- long_长整型
- long long_更长整型
- float_单精度浮点型
- double_双精度浮点型
其中整型 提供了四种不同的数据类型,在使用时可以按照需求来选择合适的类型
基本数据类型的使用方法
1. 先定义后初始化
int age,number;
age = 0;
number = 15;
2.定义并初始化
int date = 6;
C语言中提供了 sizeof()函数可以用来查看该数据类型所占空间的大小
#include <stdio.h>
int main()
{
printf("%d",sizeof(int));
return 0;
}
2. 常量和变量其生命周期和作用域
常量
常量是指一旦定义就无法修改的值,一旦出现修改操作,程序运行会报错
定义的常量的方法
1. 字面常量(使用较少)
int main()
{
30; //整型的字面常量
'w';//字符型的字面常量
15.5;//浮点型的字面常量
return 0;
}
2.const修饰的常变量(用const修饰的变量是无法被修改的,不过这个常变量本身还是一个变量)
const int age = 10;
3.#define 定义的标识符常量
#include <stdio.h>
#define MAX 1000 //标识符常量
int main()
{
return 0;
}
4.枚举常量(固定的一个或者多个值)
enum SEX //枚举类型的常量
{
MALE,
FEMALE,
SECRECY
};
int main()
{
SEX sex;//定义一个枚举类型的变量
sex = MALE; //变量的赋值仅可以是该枚举中定义的几个选择
return 0;
}
变量
在需要时可以随时被修改的数据的称为变量
int age = 18;//定义一个变量
age = 20;//修改该变量的值
变量又分为全局变量和局部变量,全局变量和局部变量可以重名但是不推荐,在重名的情况下访问变量,遵循就近原则!
int age = 20;//定义一个全局变量
int main()
{
int age = 18;//定义一个变量
return 0;
}
生命周期和作用域
生命周期
局部变量的生命周期在进入该代码块时变量生效,该代码块结束后变量销毁
全局变量的生命周期在程序开始时生效,程序结束后销毁
作用域
局部变量仅在代码块中生效,出了代码块后无法调用
全局变量在整个工程中的各个位置都可以调用(有时候需要声明)
3. 字符串和转义字符
字符串
C语言的中的字符串需要使用字符数组来存储且需要以\0 来结尾表示字符串结束
int main()
{
char arr1[] = "MAX"; //默认存在\0
char arr2[] = { 'M','A','X' }; //没有\0
printf("arr1:%s \n", arr1);
printf("arr2:%s \n", arr2);
return 0;
}
输出结果
char arr2[] = { 'M','A','X' }; 这种定义方式需要手动添加'\0'结尾表示结束,否则就会如图所示会一直往后输出知道内存中出现对应\0的数值才停止
strlen()函数
C语言中提供了strlen()函数 可以判断字符串的长度 返回值为int
转义字符
转义字符 | 释义 |
\? | 在书写连续多个问号时使用,防止他们被解析成三字母词 |
\` | 用于表示字符常量` |
\" | 用于表示一个字符产内部的双引号 |
\\ | 用于表示一个反斜杠,防止它被解释为一个转义序列符 |
\a | 警告字符,蜂鸣 |
\b | 退格符 |
\f | 进纸符 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ddd | ddd表示1~3个八进制的数字。如:\130X |
\xdd | dd表示2个十六进制数字。如:\x30 0 |
4. 选择语句
选择语句即字面意思:代码可以根据某种条件判断,来选择接下来要执行哪段代码,程序设计语言默认都是从上而下依次执行代码指令的(顺序结构),但是仅仅顺序结构是无法满足代码需求
if语句
if (条件判断)
{
//条件为真进入该代码块执行代码,条件为假,则跳过这个代码块
}
else if (条件判断) {
//条件为真进入该代码块执行代码,条件为假,则跳过这个代码块
}
else {
//上面所有的条件判断为假,则执行该段代码块
}
如果条件判断后面只有一条语句可以不写大括号(注意:else只和离他最近的if做绑定)
if (age >= 18)
printf("老板晚上好!欢迎进入***会所!");
else
printf("给你一块钱,去坐摇摇车!");
可以嵌套使用,可以有多个else if分支判断
switch语句
和if一样作用只是写法不同 if是条件判断,switch是直接提供选择项
#include <stdio.h>
#include <string.h>
int main()
{
int day = 118;
switch (day) {//传入选项的序号 1,2,3,4.....
case 1://选择项1
printf("周一");
break;
case 2://选择项2
printf("周一");
break;
case 3://选择项3
printf("周一");
break;
case 4:
printf("周一");
break;
case 5:
printf("周一");
break;
case 6:
printf("周一");
break;
case 7:
printf("周一");
break;
default:
printf("输入的值只能是1~7的范围! \n");
}
return 0;
}
注意:
传入Switch()的变量必须是一个整型,不能是其他类型,选择项也一样必须是整数类型的字面量 : case 1 case 2 case 3 ..................... 否则报错
每个选项项都要以break结尾,否则该选择项执行完后,会继续向下执行知道执行完后面的所有选择项或者遇到break才会停止
defuault 关键字 用来收尾,当用户输入的数值非选择项中提供的数值就会执行defualt下的代码,当defualt放在最后的时候可以不写break;
5. 循环语句
和打游戏一样,先重复的刷怪升级,吸取经验,直到要求的等级条件满足后,开启新的副本,进入新的区域
while循环
int i = 0;
while (i <= 10)//条件判断 是否为真 真则进入,假则跳过
{
//循环执行的代码
printf("欢迎光临!");
i++;
}
i++ 用于改变条件判断的值,如果i 不做调整 则 i <=10 为真恒成立,会进入到一个死循环
For循环
for (int i = 0 ; i<=10 ; i++)
{
//要循环的内容
}
For循环中条件的 判断,定义,调整 都在小括号中
For循环写死循环的方法
for (;;) {
//要循环的内容
}
do while循环
Do while 是先执行一次循环体内的代码在进行条件判断的
int i = 0; //定义的一个用来判断的变量
do {
//要循环的内容
i++; //对用的判断的变量做调整
} while (i < 100);
循环语句的重要关键字
Break;
直接结束循环跳出该循环执行后面的代码
continue;
结束当前循环,开始下一次的循环
6. 函数
高级程序设计语言中,都提供有官方收录或编写的库函数,可以帮助开发者完成一些特定功能 C语言中的main() printf() scanf()这些新手必会的 都是函数, 我们不需要知道prntf()是怎么实现将字符输出到屏幕上的,只需要知道 printf()如何使用就可以将输入的内容输出到屏幕,
当然我们也可以自定义一个函数,来实现我们想要完成的功能
函数是什么
维基百科中函数的定义是:子程序
是一个大型工程中的一小部分代码,子程序相对独立,可以独自完成一些特定的工作
子程序的特点: ①有返回值 ②可以传入参数 ③ 处理方式是隐藏起来的对外不可见(封装)
自定义一个函数
自定义函数和官方函数库中函数,一样的编写方式也是一样的调用
格式: 返回类型 函数名 (形参){
函数体(该函数要实现的具体功能)
}
//函数说明: 传入两个整数,返回值为这两个整数的和 返回类型int
int sum(int a, int b) {
return a + b;
}
int main()
{
int number = sum(15, 20); //调用函数传入实参,并将函数返回值赋值给变量number
printf("结果为:%d", number);//输出 结果为:35
return 0;
}
函数的形参和实参
定义函数时里小括号里面的是形参
调用函数时,小括号中填写的参数就是实参
而参数的传递有两类 一种是传值 一种是传值,根据要处理的问题情况选择即可
举例 : 实现两个数值的交换
要实现这个功能 使用传值操作是无法实现的!这里涉及到存储地址的问题,虽然实参是 a,b 函数中也是a,b 但是这两组ab指向的地址是不同的,可以理解成,调用的函数中的ab 是实参ab的一个复制品,该函数也确实实现了 两个变量交换值,但是交换的是复制品的那一组a,b, 函数外部的ab始终没变
#include <stdio.h>
#include <string.h>
//函数说明: 传入两个整数,实现交换的功能,无返回值
void flips(int a, int b) {
int temp = 0; //创建一个临时变量来辅助完整交换工作
temp = a;//三步完成交换 不需要返回值
a = b;
b = temp;
}
int main()
{
int a = 5;
int b = 15;
flips(a, b);
printf("a的值为 : %d b的值为 : %d \n");
return 0;
}
所以这种情况下需要传地址 才可以对同一地址下的内容进行修改
#include <stdio.h>
#include <string.h>
//函数说明: 传入两个整数,实现交换的功能,无返回值
void flips(int * pa, int* pb) {
int temp = 0; //创建一个临时变量来辅助完整交换工作
temp = *pa;//三步完成交换 不需要返回值
*pa = *pb;
*pb = temp;
}
int main()
{
int a = 5;
int b = 15;
flips(&a, &b);
printf("a的值为 : %d b的值为 : %d \n", a ,b);
return 0;
}
嵌套和链式调用
函数时一段相对独立的代码,他不可以嵌套定义,比如在一个函数中定义另一个函数,这是绝对不允许的,但是函数时可以嵌套调用的,例如 A函数中调用B函数,B函数中调用C函数
链式调用:将函数的返回值作为另一个函数的实参
printf("%d", printf("%d", printf("%d",42)));
prinft()函数官方文档中说明 返回的是字符的个数 所以最后输出结果为: 4221
函数的声明和定义
如果调用函数在函数定义之前,需要在调用前声明函数,因为程序是自上而下执行的,编译器在执行到调用函数这一段代码时,并没有发现函数的定义,会报错,所以提前声明的目的是为了告诉编译器,我们有这个函数,不用报错提醒
标准化的函数使用
正经编写程序时,使用的都是模块化管理,函数的声明在头文件中,编写在c文件中,调用在主函数中
举例:如果在交易中不想出售源码 就是打包代码成静态库,只提供静态库文件和头文件即可,买家通过头文件查看函数如何使用即可,而函数打包成静态库后 是无法查看c文件的
头文件中声明
c文件中编写函数的功能
主函数中调用 要先导入头文件才可以使用
函数的递归
使用递归必须要有一个限制调用的条件
每次调用后两件个条件的判断会越来越接近,直到不满足
逻辑图表示(内存布局暂时略过)
编写一个可以计算字符串长度的函数,使用递归解决
#include <stdio.h>
#include <string.h>
int strleng(char str[]) {
int i = 0;
if (*str != '\0') {
i = strleng(str + 1) + 1;
}
return i;
}
int main()
{
char str[] = "open";//字符长度为4
//系统库的sizeof()提供了判断类型大小的方法,刚好和字符串char[]一个字节一个字符
//printf("%d", sizeof(str));
//使用递归来解决
strleng(str);//数组名就是该数组的首地址所以这是一个传地址操作
printf("长度为: %d", strleng(str));
return 0;
}
逻辑示意图
或者更简洁一点
int strleng(char str[]) {
if (*str != '\0')
return strleng(str + 1) + 1;
else
return 0;
}
递归虽然好用,但是逻辑复杂了,解决问题的时候很难想到,先能做到看懂别人写的就行了,慢慢来
7. 数组
一组相同数据类型的元素的集合
数组的定义
int arr[10] = { 0 };//定义一个数组大小为10个int 并初始化全部为0
int arr1[10] = { 1,2,3,4,5,6 };//定义一个数组大小为10个int 并初始化部分空间
int arr2[10];//定义了一个数组没有初始化
arr2[3] = 8;//将下标为3的位置初始化为8 默认下标从0开始
int arr3[] = { 1,2,3,4,5,6 };//定义一个数组并初始化,数组大小由初始化的数量决定
8. 操作符
算数操作符
操作符 | 说明 |
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法 |
% | 取余 |
移位操作符
操作符 | 说明 |
>> | 右移运算符 |
<< | 左移运算符 |
int i = 1;
int j = i>>1 ;//往右移 1 位
// 1 内存中表示 00000000 00000000 00000000 00000001
// 右移 1 位后 00000000 00000000 00000000 00000000
//所以输出结果为 0
printf("%d", j);
int i = 1;
int j = i<<1 ;//往左移 1 位
// 1 内存中表示 00000000 00000000 00000000 00000001
// 左移 1 位后 00000000 00000000 00000000 00000010 低位默认补零
// 输出结果为 2
printf("%d", j);
位操作符
操作符 | 说明 |
& | 按位与(两个二进制数做比较 : 两个都为真结果为真) |
| | 按位或(两个二进制数做比较 : 一个为真结果就为真) |
^ | 按位异或(单个二进制数 : 真变假,假变真) |
赋值操作符
操作符 | 说明 |
= | 赋值 |
+= | 相加后赋值 a += 1 等价于 a = a+1 |
-= | 相减后赋值 |
*= | 相乘后赋值 |
/= | 相除后赋值 |
^= | 按位异或后赋值 |
|= | 按位或后赋值 |
>>= | 右位移后赋值 a >>=1 等价于 a = a>>1 |
<<= | 左位移后赋值 |
单目操作符
操作符 | 说明 |
! | 逻辑取反(真变假,假变真) |
- | 负号 |
+ | 正号 |
& | 取地址符 |
sizeof() | 求长度符 |
~ | 一个数的二进制按位取反 |
-- | 自减 |
++ | 自增 |
* | 间接访问操作符 |
(类型) | 强制转换类型 |
关系操作符
操作符 | 说明 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
== | 全等于 |
!= | 不等于 |
逻辑操作符
操作符 | 说明 |
&& | 并 两个都为真即为真 |
|| | 或 一个为真即为真 |
三目运算符
条件 ? 真 : 假
printf("%s", 5 > 10 ? "正确!" : "错误!");//条件判断为真即返回正确 假返回错误
逗号表达式
并不常见;
int a = 1;
int b = 3;
int c = 3;
int d = (++a, b + a, c + b);//逗号表达式会从左往右运算,直到求出最后一个结果后在赋值
printf("%d", d); // c+b = 6 即输出值为6
9. 常量和宏
已知常量可以通过 #define 来定义,但是其不仅可以定义常量还可以定义宏
宏定义又称为宏替换是一种预处理指令
#include <stdio.h>
#include <string.h>
#define AGE "今晚打老虎" //定义了一个宏 也可以理解成定义了一个常量 按需使用
#define ADD(x,y) x+y
int main()
{
char motto[] = AGE; //默认将全局的AGE替换字符串:"今晚打老虎" 不过字符串中不生效
printf("motto : %s \n", motto);
int i = ADD(15, 20); //等价于 int i = 15 + 20 看着像调用函数,但根本不是,只是做了ADD关键字的替换
printf("i=%d \n",i);//输出35
//证明是替换
i = 2 * ADD(15, 20); //等价于 i = 2 * 15 + 20 输出50 而非 70
printf("i=%d \n", i);
return 0;
}
注意事项:
宏名一般用大写宏定义末尾不加分号;
可以用#undef命令终止宏定义的作用域
宏定义可以嵌套
字符串“”中永远不包含宏
还有一些偏门用法不用记
10.指针
指针是编程语言中的一个对象,存放一个地址在他的内存空间,可以通过这个地址直接访问到计算机存储器中某个地址的值
基础的使用
1. 首先定义一个普通变量
nt a = 10;
2. 在定义一个指针变量 *说明这是一个指针 int说明该指针是要指向int类型的地址的 p就是是变量名
int* p;
3. &取地址符 将变量a的地址存放到 指针变量p中
p = &a;
4. * 是单目操作符 作用是解引用 *p 解析p中存放的地址并进行访问 *p == a
printf("%d \n",*p);
5. 所以直接对*p赋值的话 a也会随之改变
*p = 15;
printf("*p: %d a: %d \n", *p,a);// 都输出15
6. 同理修改a也是一样的
a = 20;
printf("*p: %d a: %d \n", *p, a);
#include <stdio.h>
#include <string.h>
int main()
{
int a = 10;//定义了一个变量
//定义了一个指针 *说明这是一个指针 int说明该指针是要指向int类型的地址的 p就是是变量名
int* p;
//* 是单目操作符 作用是解引用
p = &a;//&取地址符 将变量a的地址存放到 指针变量p中
//*p 解析p中存放的地址并进行访问 *p == a
printf("%d \n",*p);
//所以直接对*p赋值的话 a也会随之改变
*p = 15;
printf("*p: %d a: %d \n", *p,a);
//修改a也是一样的
a = 20;
printf("*p: %d a: %d \n", *p, a);
return 0;
}
指针类型的大小
指针类型的大小由计算机的寻址能力来决定,因为指针类型存放的就是地址,所以只需要满足该计算机的最大寻址范围即可,32位的计算机地址总线为32位,即最大寻址范围为4个字节 所以32为计算机上sizeof返回的结果是 4字节 同理64位的就是8个字节
为什么要指定指针类型
指针的大小由计算机的寻址能力决定,同一台计算机中,他们的大小一样,存储的值也是一样都是地址,那为什么要区分指针的类型,为什么没有通用的类型
指定指针的类型是为了告诉指针该如何访问内存单元,例如 int 4个内存单元 char 1个内存单元
当我们使用char 类型的指针 访问 int 类型的指针时, 他会以为该地址存放的就是一个char类型的数据,所以只访问一个内存单元就会结束, 而用int 类型的指针,访问 char类型的地址时,指针会认为这里存放的是一个int类型的数据,访问四个内存单元,这就是指定指针类型的用处
现在编译器会直接爆红,不用刻意关注
指针的运算
指针可以进行 加 或 减的运算
指针的加运算,关键在于指针的类型, int类型举例 int类型的指针 +1即向后偏移4个字节
arr的地址 : 0x000000811b0ffc88
指针 p +1后地址为 : 0x000000811b0ffc8c
指针的减运算,需要是同一空间的中的指针, 指针 - 指针是求这两个指针的元素个数
#include <stdio.h>
#include <string.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9};
int* p = arr; //数组名就是数组第一个元素的地址 即下标为0 的元素
printf("%d \n", *(p + 8) - *p); //输出8
printf("%d \n", *(p+8) - *(p+1)); //输出7
return 0;
}
11. struct结构体
在C语言中算是比较重要的一个知识点,类似于高级语言的类和对象
创建一个结构体类型
Struct结构体可以用来定义一个复杂的类型
例如要创建一个学生类型
学生应该包含 年龄 性别 成绩这些属性 统称成员变量
struct Student //定义了一个学生类型的结构体
{
//学生类型包含的属性例如: 年龄 性别 成绩 这个三个统称成员变量 在结构体中定义
int age;
char sex;
double grades;
};
创建结构体类型的变量
使用一个创建好的结构体类型,和我们使用普通数据类型一样,通过结构体类型创建变量并初始化即可
struct Student one = { 18,'N',95.5 };//创建一个学生类型的变量,并初始化它的成员变量
也可以逐个初始化
struct Student one;//创建一个学生类型的变量
one.age = 18;
one.grades = 95;
one.sex = 'N';
区别在于一个需要按照顺序初始化,一个不需要
调用结构体类型变量的成员方法
1 . 操作符
printf("one:%d_%c_%lf \n", one.age, one.sex, one.grades);
2 -> 操作符 需要配合指针类型使用
struct Student* p;//创建一个Student类型的指针
p = &one; //指针指向同类类型的变量one
printf("one:%d_%c_%lf \n", p->age,p->sex,p->grades);
3 *解引用操作符(不推荐_麻烦)
printf("one:%d_%c_%lf \n", (*p).age, (*p).sex, (*p).grades);
#include <stdio.h>
#include <string.h>
struct Student //定义了一个学生类型的结构体
{
//学生类型的复杂对象应该包含的属性例如: 年龄 性别 成绩 这个三个统称成员变量 在结构体中定义
int age;
char sex;
double grades;
};
int main()
{
//结构体构建完成后就可以使用创建的结构体类型来定义变量了
struct Student one;//创建一个学生类型的变量,并初始化它的成员变量
one.age = 18;
one.grades = 95;
one.sex = 'N';
//调用成员变量的方法
//1 .操作符
printf("one:%d_%c_%lf \n", one.age, one.sex, one.grades);
//2 ->操作符 需要配合指针类型使用
struct Student* p;//创建一个Student类型的指针
p = &one; //指针指向同类类型的变量one
printf("one:%d_%c_%lf \n", p->age,p->sex,p->grades);
//3 *解引用操作符(不推荐_麻烦)
printf("one:%d_%c_%lf \n", (*p).age, (*p).sex, (*p).grades);
}
Struct类型的大小
struct Student //定义了一个学生类型的结构体
{
int age;
char sex;
double grades;
};
sizeof()函数求大小
printf("Student类型的大小: %d 字节", sizeof(Student));
认为 Student = sizeof(int) + sizeof(char) + sizeof(double) = 13字节 那就错了!
Struct的大小是如何计算的