//标准形式
#include
int main()
{
return 0;
}
//打印hello world
#include
int main()
{
printf("hello world");
return 0;
}
观察上述代码,我们可以得出以下C语言特性:
在解决上述问题前,先介绍一个单目操作符——sizeof(),注意!!,sizeof是一个操作符,不是函数!!
作用:用于计算大小(单位为字节)
涉及到单位,我们就不得不讲讲计算机中的存储单位了,我们都知道计算机存储时是以二进制存储的,下面是关于计算机单位的类别:
之间的关系:
了解清楚以上知识后,我们就可以解决问题2了,以下我们用代码说话:
//计算每一种类型的大小(字节)
#include
int main()
{
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
printf("%d\n", sizeof(long double));//更高精度的浮点型
return 0;
}
我们发现一个问题,long称为长整型,为什么结果和int一样都为4字节呢?
因为在C语言标准下:sizeof(long) >= sizeof(int),因此long与int可以相等。
对于问题1,为什么出现这么多的类型,是因为在现实生活中,有着许多不同的物品,存在这么多的类型,其实是为了更加丰富的表达生活中的各种值,而最重要的原因是为了合理利用空间,以达到节省空间的效果。
还需要注意的一个点是关于汉字是否能用char类型进行存储,我们用代码验证:
可以看到是不行的,因为一个汉字是占用2个字符的,因此要用字符串进行存储:
生活中的有些值是不变的,如:圆周率,性别,身份证号码,血型等等;有些值是可变的,如:年龄,体重,薪资
下面将介绍C语言中的常量与变量。
1、定义方法:类型 变量名 = 数值
//定义变量的方法
#include
int main()
{
char ch = 'w';
int num = 20;
float m = 45.5f;
return 0;
}
2、变量的命名
3、变量的分类:
//局部变量
#include
int main()
{
int global = 2023;
printf("%d\n", global);
return 0;
}
//全局变量
#include
int global = 2023;
int main()
{
printf("%d\n", global);
return 0;
}
如果当全局变量与局部变量同名时,该执行哪一个呢?通过代码验证:
//当全局变量与局部变量同名时
#include
int global = 2023;
int main()
{
int global = 2022;
printf("%d\n", global);
return 0;
}
可以得到,全局变量和局部变量的名字可以相同,但是在使用时局部优先。
4、变量的使用
//变量的使用
//简单的两数求和
#include
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &num1,&num2);
sum = num1 + num2;
printf("sum = %d\n", sum);
return 0;
}
观察上述代码,这是一个求两数之和的代码,需要我们输入两个输,在输出两数之和,这里我们介绍一下输入函数——printf,输出函数——scanf。
printf:
C语言自带的一个库函数,其使用前需要包含头文件——stdio.h,用于输出内容到控制台。
scanf:
C语言自带的一个库函数,其使用前需要包含头文件——stdio.h,用于输入数据
5、变量的作用域和生命周期
作用域:通俗点讲就是该量在此处能不能用
生命周期:通俗点讲就是该量在此处还能不能用
局部变量的作用域:是局部变量所在的局部范围,即{}内
//局部变量作用域
#include
int main()
{
int a = 10;
printf("%d\n", a);
{
int b = 10;
printf("%d\n", b);
}
printf("%d\n", b);
return 0;
}
此原因是因为局部变量的作用域为局部范围内,即{}内,出了{}后不能使用。
全局变量的作用域:全局变量的作用域是整个工程
#include
int a = 10;
void test()
{
printf("%d\n", a);
}
int main()
{
printf("%d\n", a);
test();
return 0;
}
可以运行,因为全局变量的作用域为整个工程。
我们思考,如果有两个源文件呢?全局变量是否还能使用?用代码说话:
add.c
int a = 10;
test.c
#include
int main()
{
printf("%d\n", a);
return 0;
}
这种有多个.c源文件的情况下,按上述代码执行是会报错的,因此在add.c定义的int a = 10全局变量,在test.c中我不认识,这时候就要用到C语言中的一个关键词—— extern,该关键词的作用是声明外部符号(也就是让本源文件认识来自外部文件的符号),则正确形式如下:
add.c
int a = 10;
test.c
#include
extern int a;
int main()
{
printf("%d\n", a);
return 0;
}
因此可以得出,全局变量的作用域是很广泛的,作用于整个项目,但随之带来的就是不够安全的缺点,造成人人都能使用的情况。
生命周期:
程序的生命周期与全局变量的生命周期是一样的,因为程序结束最终是执行完main函数中的return 0
1、常量的分类
下面将一一介绍:
1、字面常量:就是字面值,如:
int main()
{
100;//整形字面值
'A';字符字面值
12.5;浮点型字面值
}
2、const 修饰的常变量:当给变量+const时,这个变量就不能被改变!!(const具有常属性),但num本质上还是变量,但从语法层面上为常量不能修改。
int main()
{
const int num = 10;
num = 20; //err
printf("%d\n", num);
}
看如下情况:数组的大小为常量,当用const修饰n时,n变为常变量,那可以作为常量作为数组的大小吗?答案是不能的,因为n本质上还是变量!
int main()
{
const int n = 10;
int arr[n] = {0};//err
}
但在有些编译器中,支持C99标准,引入了变长数组的语法,看如下:
#include
int main()
{
int n = 10;
int arr[n];//变长数组
}
上述代码是正确的,可以将变量作为数组的大小,但需要注意的是变长数组不能初始化
3、#define 定义的标识符常量
#include
//可定义在全局
#define MAX 1000
int main()
{
//也可定义在局部
#define MAX 1000
}
4、枚举常量
枚举关键字——enum,枚举的意思是把可能的取值一一列举出来
如性别是可以一一列举出来的、三原色也可以
#include
//声明的一个表示性别的枚举类型
enum Sex
{
//下面三个符号是Sex这个枚举类型的未来的可能取值
//枚举常量
MALE,
FEMALE,
SECRET
};
int main()
{
enum Sex s = FEMALE;
printf("%d\n", MALE);//0
printf("%d\n",FEMALE);//1
printf("%d\n",SECRET);//2
FEMALE = 5; //err 常量不能修改
}
枚举常量也是有值的,如果创建枚举常量时都没有给定初始值,则按先后顺序,从0依次往后,如果给定初始值,则取初始值,如给定个别枚举常量初始值,见如下:
#include
//声明的一个表示性别的枚举类型
enum Sex
{
MALE = 5,
FEMALE,
SECRET
};
int main()
{
enum Sex s = FEMALE;
printf("%d\n", MALE);//5
printf("%d\n",FEMALE);//6
printf("%d\n",SECRET);//7
}
可以得出,在其之后没有赋初始值的枚举常量是依次递增的。
由多个字符组成的一串字符称为字符串,C语言用""引起来的一串字符表示字符串。注意:字符串的结束标志是一个’\0’,不做展示,如:(字符串用字符数组来表示)
形式1:char arr1[] = "abc"; => 'a','b','c','\0' (因此这个字符数组为4,包含'\0')
形式2:char arr2[] = {'a', 'b', 'c'}; => 'a','b','c' (因此这个字符数组为3,不包含'\0')
//突出字符串结束标志:'\0'的重要性
#include
int main()
{
char arr1[] = "bit";
char arr2[] = { 'b', 'i', 't' };
char arr3[] = { 'b', 'i', 't', '\0' };
printf("%s\n", arr1);
printf("%s\n", arr2);
printf("%s\n", arr3);
return 0;
}
字符串末尾隐藏着一个’\0’,作为结束标志。通过上述代码可以看出,arr1与arr3正常打印,arr2打印出随机值,这是因为%s为打印字符串,会寻找’\0’,当寻找到’\0’后,打印出’\0’之前的值,而arr2中寻找不到’\0’,故会一直往下打印,打印出随机值。
这里我们介绍一个库函数——strlen(),用于计算字符串长度,其以’\0’为结束标志,计算’\0’之前的字符个数。
//strlen() —— 计算字符串长度
#include
int main()
{
char arr1[] = "bit";
char arr2[] = { 'b', 'i', 't' };
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
return 0;
}
转义字符 释义
\? 在书写连续多个问号时使用,防止他们被解析成三字母词
\' 用于表示字符常量'
\“ 用于表示一个字符串内部的双引号
\\ 用于表示一个反斜杠,防止它被解释为一个转义序列符。
\a 警告字符,蜂鸣
\b 退格符
\f 进纸符
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ddd ddd表示1~3个八进制的数字。 如: \130 == 'X'
\xdd dd表示2个十六进制数字。 如: \x300
这里重点关注:
\ddd ddd表示1~3个八进制的数字。 如: \130 == 'X'
\xdd dd表示2个十六进制数字。 如: \x30
// —— 这种注释为C++语言的注释风格,不支持嵌套注释
/* */ ——这种注释为C语言的注释风格,但不支持嵌套注释
这里不作过多讲解,后面博客会单独讲解!
#incude<stdio.h>
int main()
{
int coding = 0;
printf("你会敲代码吗?(选择1 or 0):>");
scanf("%d", &coding);
if(1 == coding)
{
printf("坚持,你会收获一个好offer\n");
}
else
{
printf("放弃,回家吃土\n");
}
return 0;
}
#incude<stdio.h>
int main()
{
int coding = 0;
printf("你会敲代码吗?(选择1 or 0):>");
scanf("%d", &coding);
sitch(coding)
{
case 1:
printf("坚持,你会收获一个好offer\n");
break;
case 0:
printf("放弃,回家吃土\n");
break;
default:
printf("无该选项\n");
break;
}
return 0;
}
这里不作过多讲解,后面博客会单独讲解!
1、while语句
2、for语句
3、do...while语句
这里不作过多讲解,后面博客会单独讲解!
#include
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &num1,&num2);
sum = num1 + num2;
printf("sum = %d\n", sum);
return 0;
}
//可以将上述代码改造为:
#include
int Add(int x, int y)
{
int z = x+ y;
return z;
}
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &num1,&num2);
sum = Add(num1,num2);
printf("sum = %d\n", sum);
return 0;
}
一组相同类型元素的集合
int arr1[10] = {1,2,3,4,5,6,7,8,9,10}; //定义了一个整形数组,这种为完全初始化
int arr2[10] = {1,2,3,4,5}; //不完全初始化,剩余5个元素会补充为0
int arr3[] = {1,2,3,4,5,6,7,8,9,10}; //可以不给定数组大小,会根据初始化个数决定数组大小
int arr4[10] = {0}; //和arr2一样,都为不完全初始化,只是第一个元素赋值为0
数组的每个元素都有一个下标,下标是从0开始的
数组可以通过下标来访问
为一块连续的空间
#include
int main()
{
int i = 0;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
for(i=0; i<10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
注意:
初始化时数组的[]表示数组的元素个数,
当使用数组时的[]为访问数组中的元素,是一个操作符
算数操作符:+ - * / %
移位操作符:>> <<
位操作符:& ^ |
赋值操作符:= += -= *= /= &= ^= |= >>= <<=
单目操作符:
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
关系操作符:
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
逻辑操作符:
&& 逻辑与
|| 逻辑或
条件操作符:exp1 ? exp2 : exp3
逗号操作符:exp1,exp2,exp3,....expN
下标引用、函数调用、结构成员访问操作符:
[] () . ->
以下为使用操作符需注意事项:
#include
int main()
{
int a = 10;
int b = 3;
printf("%d\n", a%b); // == 3
float c = 3.33f;
printf("%d\n", a%c); // err 报错
return 0;
}
#include
int main()
{
int a = 10;
int b = 3;
printf("%d\n", a/b); // == 3
return 0;
}
如果想得到小数,除号两端必须有一端为小数:
#include
int main()
{
int a = 10;
float b = 3.3f;
printf("%.2f\n", a/b); // == 3.33
return 0;
}
int main()
{
!a,-a,+a,*a ——>只有一个操作数
a + b ——>+两边有两个操作数,为双目
}
注意:sizeof为单目操作符,并不是关键字!!!,是一种操作符
sizeof(数组名) ——>表示计算数组总大小(字节),如int arr[10];结果就为40,因为int字节为4,数组有10个元素,因此40,经常计算元素总个数的方法:
sizeof(arr) / sizeof(arr[0]);
&&——>逻辑与(并且);||——>逻辑或(或者)
逗号表达式的特点是:表达式从左向右依次计算,但是整个表达式的结果是最后一个表达式的结果。
1、auto很少使用,表示一种变量的存储类型,表示自动,如局部变量是自动创建自动销毁的,正确写法为:auto int a = 10; 只是可以省略
2、break — 循环、Switch语句
3、continue — 循环
4、extern用于外部符号声明
5、register表示变量的存储类型,表示寄存器,修饰变量会被放到寄存器中
6、signed — 有符号的
7、unsigned — 无符号的
8、static — 静态的
9、struct 结构体
10、typedef — 类型重命名
11、union — 联合体
//关于auto
int main()
{
auto int a = 10;
printf("%d\n", a);
//等价于:
int a = 10;
printf("%d\n", a);
return 0;
}
局部变量默认省略auto,其作用是进入自动创建,结束自动销毁。
#include
int main()
{
register int a = 100;
}
typedef — 类型定义(简单来说就是将类型重命名)
#include
typedef int uint;
int main()
{
//等价!
int a = 100; => uint a = 100
}
static —— 静态的
作用:
1.修饰局部变量
2.修饰全局变量
3.修饰函数
1.修饰局部变量(改变了局部变量的生命周期,出了作用域依然存在,到程序结束,生命周期才结束)
void test()
{
int a = 1;
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
while(i < 10)
{
test();
i++;
}
}
test函数中的局部变量a,每次调用都会创建与销毁
结果为10个2,为什么?因为int a = 1;为函数test里的局部变量,每次调用会创建,出函数会销毁,因此每次都是一样的值,再看如下代码:
void test()
{
static int a = 1;
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
while(i < 10)
{
test();
i++;
}
}
结果为2~11,利用static修饰局部变量时,该局部变量出了函数不销毁,保留上一次值,存储位置发生变化,使得局部变量出了作用域不销毁,影响了生命周期。因为一个普通的局部变量是放在栈区的,当被static修饰时,放在静态区。但需要注意,被static修饰的局部变量是可以修改的!
2.修饰全局变量(外部链接失效,只能在本源文件使用,不能在其他源文件内使用)
add.c文件
int g_val = 2023;
test.c文件
extern int g_val;
int main()
{
printf("%d\n", g_val);
}
代码正确,可以使用int g_val = 2023; 因为全局变量具有外部链接属性,只需要extern声明,就可以跨文件使用。
add.c文件
static int g_val = 2023;
test.c文件
extern int g_val;
int main()
{
printf("%d\n", g_val);
}
当使用static修饰add.c文件中的全局变量时,外部链接属性就失效了,变成了内部链接属性,故在test.c文件中使用不了,影响了变量的作用域
3.修饰函数(外部链接失效,只能在本源文件使用,不能在其他源文件内使用)
Add.c文件
int Add(int x, int y)
{
return x + y;
}
test.c文件
extern int Add(int, int); //函数声明,形参中可只告诉类型
int main()
{
int a = 10;
int b = 20;
int sum = Add(a, b);
printf("%d\n", sum);
return 0;
}
代码正确,可以使用函数 ,因为函数具有外部链接属性,只需要适当的声明,就可以跨文件使用。
Add.c文件
static int Add(int x, int y)
{
return x + y;
}
test.c文件
extern int Add(int, int); //函数声明,形参中可只告诉类型
int main()
{
int a = 10;
int b = 20;
int sum = Add(a, b);
printf("%d\n", sum);
return 0;
}
当使用static修饰add.c文件中的函数时,外部链接属性就失效了,变成了内部链接属性,故在test.c文件中使用不了,影响了函数的作用域。
另外再补充内存中的存储空间区域:
栈区 —— 局部范围,使用后就销毁
静态区 —— 程序结束生命周期才结束
1.定义常量:
#define M 100
#define STR "hello world"
int main()
{
printf("%d\n", M);
printf("%s\n", STP);
}
2.定义宏:(跟函数差不多,可以传参)
#define MAX(x, y) (x>y?x:y)
int main()
{
int a = 10;
int b = 20;
int m = MAX(a, b);
printf("%s\n", STP);
}
3.宏不考虑类型,其实是一种替换,具体看如下:
#define M 100
#define STR "hello world"
int main()
{
//printf("%d\n", M);
//printf("%s\n", STP);
printf("%d\n",100);
printf("%s\n","hello world");
}
#define MAX(x, y) ((x)>(y)?(x):(y))
int main()
{
int a = 10;
int b = 20;
//int m = MAX(a, b);
int m = ((a)>(b)?(a):(b));
printf("%s\n", STP);
}
谈到指针,就要先了解内存的概念。内存就如酒店房号,可以通过房号精准的找到。
并且,1个内存单元为1字节
1.打印变量地址
变量是存储在内存中的(在内存中分配空间的),每个内存单元都有地址,所以变量也是有地址的,我们可以取出变量的地址看看:
//指针的使用
int main()
{
int a = 10;
printf("%p\n", &a); //%p 用于打印地址
return 0;
}
我们知道一个内存单元为1字节,当存储int类型的变量时,需要4个字节。当我们取该变量地址时,往往是拿取4个字节中,最小的地址。
2.指针的大小(64位——8字节,32位——4字节)
//指针的大小
int main()
{
int a = 10;
float b = 2.2f;
double c = 3.3;
char ch = 'W';
int* p1 = &a;
float* p2 = &b;
double* p3 = &c;
char* p4 = &ch;
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(float*));
printf("%d\n", sizeof(double*));
printf("%d\n", sizeof(char*));
return 0;
}
我们发现不管什么类型的指针,计算大小均为4字节,故指针的大小跟类型无关,再看如下:
为什么又变为8字节呢?这是因为在计算机位数为64位时,指针的大小为8字节,而在32位时,指针大小为4字节。对应的在编译器上就是64位——x64,32位——x86
3.指针的定义与使用
//指针的使用
int main()
{
int num = 10;
int* p = #
*p = 20;
printf("%d\n", *p);
return 0;
}
故可以通过指针获取变量的地址,然后再进行修改。
上述代码中,p为指针变量,用于存储地址,*p 等价于 num。
关键字:struct
当要描述一个复杂对象时,就不能简单的使用单个的内置类型来描述,故提供了结构体来描述复杂对象。如:
//声明结构体类型
struct Student
{
char name[20];//名字
int age;//年龄
char id[15];//学号
float score;//成绩
};
int main()
{
//结构体初始化:{},按照顺序以逗号隔开
struct Student s1 = {"张三", 20, "2022010823", 88.5f};
//打印结构体数据(.(访问操作符))
printf("%s %d %s %f\n", s1.name, s1.age, s1.id, s1.score);
//通过指针形式,方法一
struct Student * ps = &s1;
printf("%s %d %s %f\n", (*ps).name, (*ps).age, (*ps).id, (*ps).score);
//通过指针形式,方法二(->箭头访问操作符,只用于指针->成员)
//可以简洁,结构体指针->成员,不需要先解引用(*)
struct Student * ps = &s1;
printf("%s %d %s %f\n", ps->name, ps->age, ps->.id, ps->.score);
}