通过对C语言的基础知识的简单介绍,对C语言有大概的了解
1.什么是C语言
首先我们常说的语言是人与人交流的方式,而C语言是人与计算机交流的一种计算机语言。
人----------------------人 人------------------计算机
汉语,英语,法语... C/C++、Java、python...
操作系统之上称为上层软件,操作系统及以下称为下层/底层,C语言擅长于底层开发(也可以写应用软件)。
图1 电脑的构成
计算机语言的发展:低级----------------->高级
二进制指令-->汇编指令(助记符)-->B语言-->C语言
1000110010010001 ADD 高级语言
统一C语言
C语言的国际标准:ANSI C C89/C90国际标准(用的多) C99 C11(不流行)
常用编译器
Clang, GCC, WIN-TC, MSVC, Turbo C
(编译:源文件通过编译链接变成可执行文件的处理过程 编译器:用来编译C代码的工具)
2.第一个C语言程序
软件 Visual Studio 官网下载链接
1.创建项目
(视图—>解决方案资源管理器)
2.创建源文件
(.c 源文件 .h 头文件)
后缀名:.cpp 编译器会按照c++语法来编译代码
.c编译器会按照C的语法编译代码
3.写代码
C语言代码中必须包含主函数(main函数),main函数是程序的入口,有且只有一个main函数。
标准的主函数写法
int main() //int 整型类型,表示main函数
{
return 0; //返回0,0是整数与前面int呼应
// C语言中约定习惯:
//程序正常运行,返回0,程序异常中止,返回1
}
古老的主函数写法(不流行不推荐用)
void main()
{
return 0;
}
第一个C语言程序
#include <stdio.h> //std标准 i输入 o输出
//stdio.h包含了用于输入和输出的函数
//printf和scanf函数都是在stdio.h头文件中声明的
int main()
{
printf("hello world\n");//print fouction
//输出函数(库函数,调用得声明)
return 0;
}
4.运行
快捷键:Ctrl+F5/Fn+Ctrl+F5
3.数据类型
数据类型 | 名称 | 占内存大小 | 能存储数据个数 |
char | 字符型 | 1byte | 28 |
short | 短整型 | 2byte | 216 |
int | 整型 | 4byte | 232 |
long | 长整型 | 4/8byte | 232/264 |
long long | 更长的整型 | 8byte | 264 |
float | 单精度浮点型 | 4byte | 232 |
double | 双精度浮点型 | 8byte | 264 |
注意:
- 浮点即小数,单精度和双精度是浮点数的表示方法,主要区别在于它们所占用的存储空间和可以表示的数值范围。
- sizeof(char)返回一个字符型数据在内存中所占空间大小,sizeof 是 C 语言中的操作符,用于获取变量或数据类型所占用的内存字节数,输出结果最好使用 %zu 。
- bit 比特位 存放一个二进制数(0/1) byte 字节 1byte=8bit
- byte KB MB GB TB PB
- 1KB=1024byte 1MB=1024KB 1GB=1024MB 1TB=1024GB 1PB=1024TB
- C语言规定:sizeof(long)>=sizeof(int)
- 精度要求高用double
- 数据类型通过给名字去创建变量,创建变量的本质就是向内存申请空间(存放数据)
4.变量和常量
变量
描述变化的量,创建变量后就会占内存空间。
变量的分类
全局变量:定义在代码块({ })之外的变量。
局部变量:定义在代码块({ })内部定义的变量。
#include <stdio.h>
int a = 100; //全局变量
int main()
{
int a = 10; //局部变量
printf("a=%d\n", a);
return 0;
}
注意:
- 当局部变量和全局变量的名字相同下,局部变量优先。
- 建议局部变量和全局变量名字不要相同。
变量的使用
// 计算2个整数的和
#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0; //初始化(不初始化,默认放随机值)
scanf("%d %d", &num1, &num2); //scanf输入函数 &取地址
printf("sum=%d\n", num1 + num2);
return 0;
}
注意:在VS中变量定义不在最前面时,会报错,此处可在程序第一行放上:
#define _CRT_SECURE_NO_WARNINGS
此处一劳永逸的做法是:
找到 newc++file.cpp文件,直接将那句话放进去。可以复制粘贴文件到桌面,改完之后直接替换掉原文件。
变量的作用域和生命周期
变量 | 作用域(哪里可用) | 生命周期 |
全局变量 | 整个工程 | 整个程序 |
局部变量 | 变量所在局部范围 | 进作用域开始,出作用域则结束 |
变量的作用域
局部变量:
#include <stdio.h>
int main()
{
{
int a = 10;
printf("a=%d\n", a); //局部变量的作用域
//可简单理解为将局部变量包含起来
//最近的两个{}的范围
}
// printf("a=%d\n", a); //此处未注释掉会出现a未定义的报错
//即不在局部变量的作用域内
return 0; //出了作用域a已经被销毁(即生命周期结束)
}
#include <stdio.h>
int main()
{
int a = 10; //此处局部变量的作用域为外面{}的范围
//两个都能正常输出
{
printf("a=%d\n", a);
}
printf("a=%d\n", a);
return 0;
}
全局变量
此处用extern声明引用另外一个源文件的全局变量a,可以正常输出三个a,充分说明全局变量是整个工程可用的。
常量
描述不变的量,在程序中不允许修改,常量未使用前不占内存空间
常量种类:
字面常量 eg:30 3.14 'q' "adf"
const修饰的常变量 const int n=10
注意:在C语言中,const修饰的n本质是变量,但是不能被修改,有了常量的属性。
#include <stdio.h>
//const修饰的常变量本质还是变量,定义数组时[]内需要一个常量
//运行后此程序在VS中跑不起来,提示代码出错
//充分说明了const修饰的常变量还是变量
int main()
{
const int n = 10;
int arr[n] = {0};
return 0;
}
#define定义的标识符常量
#include <stdio.h>
#define MAX 100
#define STR "abcdef"//#define定义的常量类型不定,可以是整型,字符串型...
int main()
{
printf("%d\n", MAX); //直接打印显示
int a = MAX; //拿来做赋值使用
printf("%d\n", a);
printf("%s\n", STR);
MAX = 20; //不注释这一行会报错,即#define定义的标识符常量不可修改
return 0;
}
枚举常量:一一列举的常量,不能修改
#include <stdio.h>
//颜色
enum Color
{
//枚举常量
RED,
GREEN,
BLUE
};
//性别
enum Sex
{
//枚举常量
MALE,
FEMALE,
SECRET
};
int main()
{
int num = 10;
enum Color c = RED;
RED = 20;
MALE = 10;
return 0;
}
注意:一般#define定义的符号常量和枚举常量都会大写。
小结:创建变量就开辟一块空间,常量可以存进变量的空间里,const修饰的常变量是被限定了不能修改常量值的一块空间,本质还是变量。
5.字符串+转义字符+注释
字符:双引号引起来的一串字符(即字符串字面值),结束标志为\0
我们敲出来的字符: #qwers ,在C语言中可以用char(字符类型)来描述字符,用单引号描述字符常量(eg 'a'),我们还可以创建一个字符变量将字符常量存起来char ch = 'w'。
字符串(双引号引起来的一串字符)eg “abcdef” 由于C语言中没有字符串类型,所以存储字符串要通过定义字符型数组。eg char arr[ ]=“abcdef”
注意:
- 不知道后面多少字符[]可以不写数字只填写字符串内容,会根据字符串内容自动确定需要多大空间
- 要在[]中填入数字一定要保证足够后面的字符存入
如何看数组里面实际存放的内容?
运行后按住F10调试,再打开调试窗口观察数组的内容
注意:发现数组里面多了一个我们没有写的字符 \0(字符串结束标志),字符串末尾隐藏放一个\0,初始化数组时,“abcdef” 6个字符需要7个字符空间存储。
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef"; //形式上的差异
char arr2[] = {'a', 'b', 'c', 'd', 'e', 'f'};//arr2中从下图
//可以看出没有\0
//\0的重要些
printf("%s\n", arr1); //从下图可以看出arr2与arr1的使用上的差异
printf("%s\n", arr2); //打印字符串时遇到字符串结束标志(\0)才结束
//由于arr2中没有\0(结束标志)
//他会持续往后在内存中找
//直到在内存某位置找到\0才能停下来
//当在arr2中主动放一个\0
//就可以实现arr1的打印效果
int len = strlen("abc"); //求字符串长度的库函数string length
//要加上头文件string.h声明
//使用:strlen(arr1)
//定义一个int变量接收函数返回值
printf("%d\n", len); //3 strlen在数字符串长度时也看\0
// 数到\0就不数了,数出字符个数不包括\0
// \0只是结束标志,数字符串长度不包含它
printf("%d\n", strlen(arr1)); //6 计算字符串长度时不数\0
printf("%d\n", strlen(arr2)); //随机值(eg:22)没有\0一直往后数
//直到找到\0,计数结束
//如果在arr2中放入\0,输出就是6
return 0;
}
注意:
- 求字符串长度时:找\0 前出现有多少个字符。
- 遇到\0不能打印后面内容,后面即使有字符也不打印了,遇到字符串结束标志就认为字符串打印结束了。
- 充分理解\0的重要性。
- 0 数字0 数值是0 ; '0' 字符0 ASCII值是48 ; '\0' 字符(结束标志) ASCII值是0;
- EOF 文件的结束标志(end of file)值是-1(右击鼠标速览定义查看)
转义字符
转变原来字符的意思(不会打印出来)
\n 换行(从普通字符n变成换行的意思)
\0 字符串结束标志(从普通数字0变成字符串结束标志)
注意:转义字符不是什么都能转义,C语言规定了一些转义字符
注意:
1.C语言中三字母词:??)--> ] ??(--> [ 防止被解析为三字母词?前加个\
eg printf("%s\n","(are you ok\?\?)");
// 如果?前不加\一些支持三字母词的编译器会输出 (are you ok]
2.%d -打印整型 %c -打印字符 %s -打印字符串 %f -打印float类型的数据
%lf -打印double类型的数据 %zu 打印sizeof的返回值
eg printf("%c\n",'‘'); //err 打印单引号字符不能直接放进单引号中,因为前面那个单引号会和中间那个单引号形成一对,最后那个单引号落单了。' 前面加\ 后就可以正常打印输出(双引号同理)
3.字符串打印:
printf("%s\n","abcdef");
printf("abcdef"); //打印字符串的简写形式(只有字符串可以这样简写,整型是不可以这样打印)
printf("A"); //此处A变成一个字符串,有结束标志,与字符A不同。字符A占1个字符空间,而字符串A占2个字符空间。
4.\\
printf(“abcd\0ef”); //输出:abcd (\0 被编译器认为是结束标志)但是我想打印出普通字符\和0,而不是当做结束标志用
printf(“abcd\\0ef”); //输出:abcd\0ef (在\前加上\,转变\意思,让\与0不能组成转义字符,让\从转义字符中\变成普通\
5.\t
printf("c:\test\test.c"); //在\前加上\,让\从制表符中的\变成普通字符\
6.\n换行 \r回车
7.\ddd 八进制数
printf("%c\n",'\130'); // \130为一个字符 意思是将八进制130转换成十进制的数字作为ASCII码值代表的字符 十进制:1*82+3*81+0*80=88 ASCII码代表的字符:X 输出:X
ASCII编码:字符难以存入(不好存为二进制),给字符编号,将编号存进去即可(用编号将字符用二进制形式储存)
8.\xdd 十六进制数
printf("%c\n",'\x60'); // x60为一个字符,意思是将十六进制x60转变成十进制数作为ASCII值代表的字符 十进制:6*161+0*160=96 ASCII码代表的字符:‘ 输出:‘
转换出的十进制保证在0-127 不能超出范围,否则没有意义
\x063编译器也是支持的 与\x63等价 两位三位都可以,但是转换成十进制不能超出ASCII码值范围(0-127)
9.转义字符整体是一个字符
例题:
printf("%d\n",strlen("qwer t")); //输出 6 (数\0之前的所有字符,空格也是一个字符)
printf("%d\n",strlen("c:\test\628\test.c"));//输出 14
转义字符整体看为一个字符,其中的\62可以整体看为一个转义字符,而不是\628,因为八进制数字是由0~7组成。要注意的是\t的效果是水平空四个格子,但是数字符串长度不是看输出的效果,就看当前字符串即可。根据下面的分析,\628看成十六进制超出ASCII码值范围,没有意义,不再是转义字符,只有看成八进制才可以看作转义字符。
根据ASCII码值范围0~127
对应的八进制范围是000~177 1*82+7*81+7*80=127
对应的十六进制范围是00~7F 7*161+F*160=127
整数部分进制转换
十进制转换为N进制方法:除以N,反向取余数,直到商为0终止
N进制转换为十进制方法:按权相加 (注意此处不考虑小数部分)
注释:注解解释(注释不要的代码,解释代码)
C语言的注释风格:/*...*/
- 一次可以多行注释
- 缺陷:不支持嵌套注释,更建议用C++的注释风格
C++的注释风格://
- 手动注释需要一行一行注释,单行注释
- VS中快捷键可以快速选中多行注释 Ctrl+K+C
- 取消多行注释 Ctrl+K+U
注释的好处:
- 梳理思路
- 对复杂的代码进行解释,容易上手,代码可读性高
注意:补充一个快捷键知识点:shift+方向键(上下左右)可以选中某区块
6.选择语句
C语言实现选择/分支:
if else Switch
#include <stdio.h>
int main()
{
int input = 0;
printf("要好好学习吗(1/0)?\n");
scanf("%d\n", &input);
if (input == 1) //表达式为真执行
{
printf("好offer\n");
}
else //表达式为假执行
{
printf("卖红薯\n");
}
return 0;
}
7.循环语句
三种形式:while for do...while
#include <stdio.h>
int main(void)
{
int i = 1;
int sum = 0;
while (i <= 100)
{
sum = sum + i;
++i;
}
printf("sum = %d\n", sum);
return 0;
}
C语言是结构化的程序设计语言
三种结构:顺序结构 选择结构 循坏结构
8.函数
简化代码,代码复用(函数有输入有输出)
#include <stdio.h>
int Add(int x, int y) // add函数名 x,y函数参数 int返回类型
{
int z = 0; //函数体({ }中)
z = x + y;
return z; //return (x+y);函数体中就写这一行即可,简化代码
}
int main()
{
int n1 = 0;
int n2 = 0;
scanf("%d %d", &n1, &n2);
int sum = Add(n1, n2); //函数写好之后可以反复调用
printf("%d\n", sum);
return 0;
}
9.数组
存单个数据:int a=1;(创建变量存储单个数字)(存放一个整型向内存申请4bytes)
存储一组数据(创建数组 数组:一组相同类型的元素的集合):int arr[10]={10,11,12,13,14,15,16,17,18,19};(多个元素用{ },存放10个整型向内存申请40bytes,数组在内存中都有一个序号,即数组的下标)
数组的下标:下标是从0开始的,通过下标访问元素 eg arr[8]=18
数组的使用:通过下标来访问元素
#include <stdio.h>
int main()
{
int arr[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
printf("%d\n", arr[8]); //打印输出一个数字
int i = 0;
while (i < 10) //打印输出整个数组(循坏方式产生下标)
{
printf("%d\n", arr[i]);
i++;
}
return 0;
}
注意:
- 数组初始化:int arr[10]; //10个元素,不给数组初始化必须指定数组大小 int arr[]={1,2,3}; //3个元素,不指定数组大小必须初始化,根据初始化内容确定数组大小。
- 例题:printf(“%d\n”,strlen("c:\test\121")); // 7
- char arr[]={'b','i','t'}; printf("%d\n",strlen(arr)); //输出结果为随机值,数组只放了三个字符,strlen函数数到\0就不数了,数出字符个数不包括\0,但是此数组中没有\0,所以strlen会一直在内存中找,直到找到\0为止因此输出结果为随机数。改进方案:char arr[4]={'b','i','t'};//输出结果为3 ,数组能放4个元素,初始化时却只放了三个元素,剩下的一个空间默认初始化为0(不完全初始化,剩余部分默认初始化为0),/0的ASCII值为0,数字0的数值为0(ASCII值为0),数字0和\0本质上没有区别,放0会被解析为\0,所以有了\0,就能输出3
- int arr[10]={0};
int arr[10]={1};
- 数组创建错误示例:int n=10; int arr[n]={0}; //err 在不支持C99编译器下,[]内需要常量,而n即使初始化也还是变量;即使在能支持C99的编译器下,也不能对数组初始化,所以怎么看都是错的。 C99标准之前的数组大小都是用常量或者常量表达式来指定的。 eg int arr[10]={0}; int arr[4+6]={1,2,3,4}; C99标准之后,支持了变长数组,允许数组大小是变量,但是这种指定方式的数组不能初始化 eg int m=100; int arr[m];(支持C99的编译器也不能在后面初始化) VS对C99一些语法支持不是很好,不支持变长数组
- 字符串数组要算上\0所占内存空间,因此数组分配空间是字符串所占空间加上\0所占的空间。
10.操作符
C语言提供了非常丰富的操作符,使用比较灵活
算数操作符:+ - * / %(取模) 注意: 1. /除号得到的是商,不关心余数 %取模得到的是余数,不关注商 2. 除号的两端是整数时,执行整数除法;两端只要有一个浮点数就执行浮点数除法。eg printf("%d\n",7/2);//输出3 printf("%.1f\n",7/2.0);//输出3.5 (%.1f 小数点后保留1位,%f小数点后保留6位) 3. 取模操作符的两个操作数只能是整数,不能是其他数字 移位操作符:>>向右移位符 <<向左移位符(涉及二进制运算)
位操作符:&与 ^异或 |或
赋值操作符:= 赋值 += -= *= /= &= ^= |= >>= <<= 注意: 1. int a=0; //初始化(在创建变量的同时给它一个值)a=20;//赋值(已经有变量之后给它一个值) 2.a=a+3; //简化:a+=3 a=a-3; //简化:a-=3
单目操作符(只有一个操作数的操作符):!- + & sizeof ~ -- ++ * (类型) 注意: 1.C语言中,0表示假,非0表示真 2.!逻辑反操作(真变成假,假变成真)-负号 +正号 &取地址 3.sizeof是单目操作符,不是函数
#include <stdio.h>
int main() //sizeof计算变量大小
{
int a = 10;
printf("%d\n", sizeof(a)); //输出都是4
printf("%d\n", sizeof(int)); // sizeof可以直接跟变量
//或者这个变量对应的类型
printf("%d\n", sizeof a); //输出为4,sizeof不是函数
//可以不加括号,函数()不能省
printf("%d\n", sizeof int); // err 虽然sizeof是操作符
//但是语法不支持int不加()
//对于类型来说,还是得加上()
return 0;
}
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%d\n", sizeof(arr)); // sizeof计算数组大小
//输出值为40,计算的是整个数组的大小
//单位是字节(这里是整型数组不要考虑\0)
printf("%d\n", sizeof(arr[0])); //输出值为4,计算一个元素大小
//一个int大小
printf("%d\n", sizeof(arr) / sizeof(arr[0])); //输出值是10
//数组的元素个数
return 0;
}
- ++ --
#include <stdio.h>
int main()
{
int a = 10;
int b = a++; //后置++,先使用后++
// int b=a; a=a+1;
printf("%d\n", b); // 10
printf("%d\n", a); // 11
int c = 10;
int d = ++c; //前置++,先++后使用
// c=c+1; int d=c;
printf("%d\n", d); // 11
printf("%d\n", c); // 11
return 0;
}
5. (类型) 强制类型转换
#include<stdio.h>
int main()
{
int a=(int)3.14;//3.14 字面浮点数,编译器默认为double型
//在前面加(int),将3.14强制类型转换为整型,只要整数部分
printf("%d\n",a);//输出3
return 0;
}
关系操作符 : > >= < <= !=(判断不相等) ==(判断相等)
#include <stdio.h>
int main()
{
int a = 10;
if (a = 3) //a=3是赋值不是判断 修改为a==3 才不打印输出
{
printf("hehe\n");
}
return 0;
}
逻辑操作符:&&逻辑与 并且 || 逻辑或 或者
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
if (a && b) // a和b都为真执行语句打印输出hehe
// a=0(假) b=20(真);a=10 b=0;a=b=0
//以上三种都不执行语句
{
printf("hehe\n");
}
if (a || b) // a或者b其中一个为真执行语句
// a=b=0不执行语句
{
printf("lala\n");
}
return 0;
}
条件操作符:exp1 ? exp2:exp3 三目操作符(有三个操作数)操作数:exp1 exp2 exp3 操作符:?: exp1为真,只计算exp2,整个表达式结果是exp2 的结果;exp1为假,只计算exp3,整个表达式结果是exp3 的结果
#include <stdio.h>
int main()
{ //求出a,b间较大值
int a = 10;
int b = 20;
int r = a > b ? a : b;
printf("%d\n", r); //输出:20
return 0;
}
逗号表达式 exp1,exp2,exp3,...expN 由逗号隔开的一串表达式 特点:从左到右依次计算,整个表达式结果是最后一个exp的结果
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 0;
int d = (c = a - 2, a = b + c, c - 3); // c=8 a=28 c-3=5
//表达式结果为
//最后一个exp结果:5
printf("%d\n", d);// 5
return 0;
}
下标引用、函数调用和结构成员操作符
下标引用操作符[ ] 访问数组元素时必须用下标
#include <stdio.h>
int main()
{
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};//这里的[]不是下标引用操作符
//定义数组的一种形式,而不是操作符
//定义数组时在不支持C99的编译器下
//[]中不能有变量
printf("%d\n", arr[3]);//访问第四个元素
//这里的[]就是下标引用操作符
//内部放的是下标
//arr和3就是[]的操作数
int n = 4; //当[]作为下标引用符访问元素时可以有变量
printf("%d\n", arr[n]);
return 0;
}
函数调用操作符( )
#include <stdio.h>
int add(int x, int y)
{
return (x+y);
}
int main()
{
int sum = add(2, 3);//这里的()就是函数调用操作符
//操作数:add,2,3
printf("%d\n", sum);
return 0;
}
注意:()不能省略掉
结构成员操作符:. ->
结构体变量访问结构体成员用.操作符;指针变量访问指针所指向结构体成员用->操作符。
11.常见关键字
C语言本身内置的,关键字不是自己创建的,也不能自己创建
auto 自动 一般省略 所有局部变量前面都有auto eg auto int a=10 (a变量进作用域自动创建,出自动销毁,属于自动变量,本质上定义局部变量前都有一个auto,实际上所有的局部变量都是auto类型的,所以将auto省略)自动变量
break 跳出循坏
常见关键字
循环关键字 | 分支关键字 | 类型关键字 | 未分类关键字 |
for | switch case default | char | const 修饰变量 常属性 |
while | if else | short int long longlong | extern声明外部符号 |
do while | goto实现跳转与分支很像 | float double | register 寄存器 限制变量存在寄存器 |
break | signed 有符号的 unsigned 无符号的 | static静态的 修饰函数和变量 限制变量存在静态区 | |
continue | sizeof计算大小(与类型有关) | volatile | |
自定义类型 enum枚举 struct结构体 union联合体(共用体) | return函数返回值 | ||
typedef类型重命名 | |||
void无(函数的返回类型,函数参数类型) |
注意:
- 变量名命名(有意义 字母数字下划线组成不能有特殊字符,不能以数字开头)
- 变量的名字不能是关键字(定义变量名不能与关键词冲突)
关键词typedef和static
typedef 类型定义(类型重命名),为类型重定义
#include <stdio.h>
typedef unsigned int uint; //简化复杂类型名,重新命名uint
/*struct Node
{
int data;
struct Node* next;
}; //结构体类型 */
typedef struct Node
{
int data;
struct Node *next;
} Node; //结构体类型
int main()
{
unsigned int num = 0; //使用类型时不方便
uint num2 = 1; //创建变量的类型与上面的类型一致
// unsigned int与uint两者等价
// struct Node n; //创建结构体类型变量不方便
Node n2; //简化后
return 0;
}
static 静态的 作用:修饰变量和修饰函数
修饰变量(静态变量)
局部变量
#include <stdio.h>
void test()
{
int a = 1; //普通局部变量
a++; //进作用域创建,出作用域销毁
printf("%d\n", a); //函数调用完a就销毁了
} //第二次进入test函数a重新创建并赋值1
//每次调用test时变量a都重新创建,不会相互影响
int main()
{
int i = 0;
while (i < 10) //循环10次输出10个2
{
test();
i++;
}
return 0;
}
#include <stdio.h>
void test()
{
static int a = 1; //static修饰的变量a出作用域不会销毁
//此时称为静态变量(放在静态区)
a++;
printf("%d\n", a);
}
int main()
{
int i = 0;
while (i < 10) //循环10次,输出2~11
{
test();
i++;
}
return 0;
}
注意
- static修饰局部变量的时候,局部变量出了作用域,不销毁,本质上,修饰局部变量时,改变了变量的存储位置,影响了变量的生命周期,生命周期变长(和程序的生命周期一样)
- C中内存划分:栈区,堆区,静态区
- 栈区(存放局部变量...):进作用域创建,出作用域销毁(栈区数据特点)
- 静态区(静态变量):出作用域不销毁,程序生命周期结束时销毁(静态区数据特点)
- 用类型定义变量(创建变量)本质就是分配空间(变量在编译期间分配空间,不需要代码执行过程中重新开辟空间来创建它 eg:全局变量,静态变量),普通局部变量是放在栈区,出作用域就会销毁,所以每次都会重新分配一次空间,而用static修饰的局部变量是放在静态区,出作用域不销毁,所以不需要重新分配空间,所以即使每次进入循环时都会有重新定义的语句,但是并不会起作用,只会执行a++。
- void 不需要任何返回(无return语句)
全局变量
普通全局变量运行后结果为:2020
static修饰的全局变量运行报错
注意:
- 调用一个定义在其他文件的普通全局变量,要用extern进行声明外部符号,而且这两个文件必须在同一个项目下
- 全局变量具有外部链接属性(外部源文件的全局变量通过链接手段被其他源文件使用),static修饰全局变量的时候,这个全局变量的外部链接属性就变成了内部链接属性(只能在当前的源文件中使用),其他源文件(.c)就不能使用这个全局变量了。(全局变量存储位置也在静态区)
- 在使用时,感觉static修饰的全局变量作用域不再是整个工程而是当前源文件中。 作用域变小的本质是全局变量的外部链接属性变成了内部链接属性。
修饰函数
普通函数运行输出:30
static修饰的函数运行报错(链接属性导致的,static修饰使函数从外部链接属性变成了内部链接属性)
注意:
函数是具有外部链接属性,被static修饰时函数的外部链接属性变成了内部链接属性,其他源文件就无法使用了。
register 寄存器
电脑存储设备:硬盘-->内存-->高速缓存(cache)-->寄存器(集成到CPU) 访问速度:慢———————————————>快 空间大造价低———————————————>空间小造价高
CPU处理速度特别快,内存的速度跟不上,为了提高处理效率,出现了高速缓存和寄存器。保证大部分数据在寄存器中,提高处理的速度。
#include <stdio.h>
int main()
{ //寄存器变量
register int num = 3; //建议3存放在寄存器中,决定不了
//最终还是编译器决定
return 0;
}
寄存器访问速度快 但是只是建议编译器怎么做(寄存器空间太小了),一般编译器都会自己主动将常用数据放进去
12.#define定义常量和宏
定义标识符常量:赋值使用或者直接输出。
#include <stdio.h>
#define NUM 100
int main()
{
printf("%d\n", NUM); //直接打印
int n = NUM;
printf("%d\n", NUM); //赋值使用再打印
int arr[NUM] = {0}; //定义数组时
//用#define定义的常量确定数组大小
return 0;
}
定义宏:有参数,宏是完成替换的
#include <stdio.h>
#define ADD(x, y) ((x) + (y)) // ADD宏名
// x,y宏的参数(无类型,实际上就是一个符号)
// x+y宏体
/*int Add(int x, int y)
{
return x + y;
} */
int main()
{
int a = 10;
int b = 20;
int c = ADD(a, b); //与函数用法相似 定义方法有差异
//宏调用是完成替换宏体
//((x)+(y)——>((a)+(b))
//编译器处理后:int c=((a)+(b))
print("%d\n", c);
return 0;
}
13.指针
内存:存储器,计算机的程序运行(内存是程序运行的环境和空间)都是在内存中完成的
如何有效管理和使用?
内存会划分为一个个小的内存单元(每个内存单元都是一个字节大小)每个内存单元都有一个编号 类比生活中将大空间划分一个个房间,给房间编号(编号相当于地址)
一个内存单元是一个字节,可以对应哪些关系呢?
32位电脑 由下面分析计算可得最大内存4G
电脑访问内存空间要生成地址(通过地址线生成地址),32位电脑有32根地址线,计算机中的信息是通过电信号形式传播,电信号只有两种状态,即开或关(高电平或低电平),也就是1或0,所以计算机中采用二进制,地址线的设计是基于二进制的,因此一根地址线只有0/1两种状态 32根地址线,根据0/1状态不同的排列,一共232种的32位二进制序列的状态,每个二进制序列对应一个数字(即编号),可以作为内存的编号存在。一个地址(编号)管理一个内存单元(1byte大小),232个地址序列最多可以管理232byte的空间,通过二进制换算成10进制位为4G。(为了方便书写可以将二进制换成16进制表示)
64位电脑 最大内存8G 16G 32G
为什么一个内存单元不是一个比特位? bit不合理 太精细化 没有必要 eg: char c;//创建一个变量c,需要一个字节(8个bit),如果最小内存单元设置为一个比特位,那么创建一个字符型变量c就需要占8bit(8个内存空间),每个空间都有一个地址,仅仅一个字符变量就有8个地址,浪费地址数,太精细化,没有必要。
为什么要讲内存?创建变量本质就是在内存申请空间
#include <stdio.h>
int main()
{
int a = 10; //向内存申请4个字节存储10
// a的地址是这四个字节中第一个字节的地址
&a; //取地址操作符
printf("%p\n", &a); //%p以地址格式打印数据,打印的就是地址
return 0; //0x0012ff48
}
如何查看变量a的地址?
①先开始调试,再通过调试窗口的监视可以查看a的地址
②先开始调试才能在内存窗口查看a的地址
在内存窗口中输入&a回车
地址框中出现的就是变量a的地址
为了方便观察可以将列调整为4(每两个十六进制数为一个字节,4列共有4个字节,此处观察的整型变量a就是占4个字节)
地址 内存中的数据 内存数据的文本解析(不准确的) (与上图对应区域说明)
a=10在内存中怎么存放的数据是00 00 00 0a而不是10?
10是十进制的 十六进制0~9 a~f 十进制10转换为十六进制数:a ,又因为10是存在整型变量中的,所以会像内存申请4个字节,4个字节就是32bit,所以10写到内存中要写32个比特位(二进制位0/1)如下: 十进制10:10
二进制10: 0000 0000 0000 0000 0000 0000 0000 1010(位数根据占内存空间确定)(每4个二进制位代表一个十六进制 因为4位二进制能表示16种状态)
十六进制10:0 0 0 0 0 0 0 a (一个十六进制位代表4个bit,我们常常将两个十六进制位写在一起代表一个字节)
10的十六进制表示:0x 00 00 00 0a (存到内存时倒着看数据)
注意:
- 当变量占多个字节(每个字节都有编号/地址),该变量地址是第一个字节的地址。
- 这里的a是局部变量,每次运行程序时a都会重新创建,所以地址是变化的
- 二进制太长,不方便,用十六进制方便简洁
- 局部变量代码运行时指派地址,全局变量代码编译时就已经指派地址
取地址操作拿到的地址就是一个16进制数值,怎么存起来? 指针变量
#include <stdio.h>
int main()
{
int a = 10;
printf("%p\n", &a);
int *p = &a; //&a要存起来,用int *类型创建一个变量p
// p就是指针变量
//内存单元的编号就是地址,地址也被称为指针
// p用来存地址(即指针)
//所以将存放指针(地址)的变量称为指针变量
return 0;
}
当p是指针变量时该如何去理解?
int a = 10; //向内存申请空间创建变量a,存放10,该内存空间的编号(地址)是首字节的编号(地址)(起始编号)
int * p = &a; //创建用来存放的地址(编号)的p变量,p的类型:int *,*说明p是指针变量,int说明p指向的对象是int类型的(p指向的对象是a,即a的类型是int类型的)
注意:
- 指针:地址 指针变量:存放(指针)地址的变量 指针变量的创建 int*
- p 指针变量类型的解析:* 说明p是指针变量,int说明p指向的对象的是int类型,指针变量不是*p,而是p,int*是类型
- 指针变量的创建
#include <stdio.h>
int main()
{
char ch = 'w';
char* pc = &ch; //创建指针变量pc
printf("%p\n", pc);
return 0;
}
存地址的意义:通过p中存放的地址(a的地址)找到p所指向的对象(a)
怎么找a?
对指针变量p进行解引用就能找到指针所指向的对象a ,即*p。*是解引用操作符,*p意为通过p中存放的地址,找到p所指向的对象a,*p就是p指向的对象a。
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
*p = 20;
printf("%d\n", a);//输出20
//*p通过p中存放的地址
//找到p所指向的对象a,修改a的值
return 0;
}
注意:
- 单目操作符:* 解引用操作符(通过地址找到对象)<-->&取地址(取出对象的地址) 一对单目操作符(功能相反,可抵消)
- 地址不能随便改动,C 语言规定了通过指针修改内存地址的操作必须谨慎使用,必须遵守内存权限规则,否则会出现各种内存安全问题。
- &a 取的是第一个字节的地址,如下图所示,0x010ff808是第一个字节0a的地址,后面三个字节地址:09 0a 0b (在内存窗口可将列调整为1观看)
指针变量的大小
指针变量的大小取决于地址的大小
#include <stdio.h>
int main()
{
printf("%zu\n", sizeof(char *)); //输出都是4/8
printf("%zu\n", sizeof(short *));
printf("%zu\n", sizeof(int *));
printf("%zu\n", sizeof(float *));
printf("%zu\n", sizeof(double *));
return 0;
}
注意:
- 不管是什么类型的指针都是在创建指针变量,指针变量是用来存放地址的,一个指针变量的大小取决于一个地址存放需要多大空间
- 32位机器的机器:地址是32bit-------4byte所以指针变量大小是4个字节, 64位机器的机器:地址是64bit-------8byte所以指针变量大小是8个字节
- sizeof返回值最好用%zu打印
- 代码风格
#include <stdio.h>
int main()
{
char* p; //创建单个指针变量将*与类型放一起更好
int* pc;
int* p1, p2, p3; //创建多个变量将*与类型放一起不好
//容易误解p1,p2,p3都是指针
//实际上只有p1是指针,p2,p3都是整型
int *pc1, pc2, pc3;//创建多个变量,将*与变量放一起更好
//p1是指针,p2,p3都是整型
int *p4,*p5,*p6; //创建3个指针变量
return 0;
}
14.结构体
常见的类型:char,int short,long,float,double 表示数字或者字符没有问题,但是不能表示复杂对象。eg:表示人 :姓名+年龄+性别+地址+电话,简单类型无法解决,C语言中就有了自定义类型,结构体(关键字:struct)是一种自定义类型
结构体是把一些单一类型组合在一起的做法
#include <stdio.h>
struct Stu //结构体类型
{
char name[20]; //结构体成员变量
int age;
char sex[10];
char tele[12]; //此时不占空间
//只有使用结构体类型去创建变量时才会向内存申请空间
};
void print(struct Stu *ps)
{
//printf("%s %d %s %s\n", (*ps).name, (*ps).age, (*ps).sex, (*ps).tele);
printf("%s %d %s %s\n", ps->name, ps->age, ps->sex, ps->tele);
//简洁写法
//-> 结构体指针变量->成员名 (前提是知道地址)
}
int main()
{
struct Stu s = {"zhangshan", 20, "nan", "15000952387"};
//创建结构体类型变量并初始化
printf("%s %d %s %s\n", s.name, s.age, s.sex, s.tele);//直接打印
//结构体变量用.操作符访问结构体成员
//结构体对象.成员名 (前提是知道结构体对象)
print(&s); //用取地址方式调用函数打印输出结构体变量中数据
return 0;
}
注意:
- 创建一个新类型时不占空间,用类型去创建变量时才会向内存申请空间
- 访问成员:①结构体对象访问成员用 . (得到结构体对象用.);②用指针访问对象的成员用->(得到地址用->)
- define不是关键字, 是一个预处理指令