目录
一、学习方法
1、上课认真听讲,及时消化,不会的及时做好记录,下来弄清楚
认真完成布置的作业,学习过程保持空杯心态,耐得住寂寞,不喊苦,不怕累,坚持学习并且学会输出,实践!!!欲戴王冠,必承其重。 如果你总是和别人走一样的路怎么才能保证超越别人,那就得付出不一样的努力!!!
2、学完一个阶段,认真归纳梳理总结博客
养成写博客的习惯,学完一个阶段,要学会写博客,主要是自己对于技术的理解、心得,梳理出需要掌握的知识点,如何使用!构成一套完整的知识体系!可以学习借鉴模仿优秀博客!
博客的重要性:
1. 自己写博客,是对所学知识的总结
2. 写博客可以记录你学习的一个过程和心得,给面试官更多了解你的机会,同时增加面试的谈资。
3. 写博客说明你是一个愿意分享的人。
3、学会使用Gitub(代码仓库、代码托管平台)
Gitub作为一个代码托管平台,可以让面试官看到付出的努力,同时这也是对于自己学习过程的一个记录!学会注册Gitub和如何提交代码到gitee。
- github网址:https://github.com/ (国外/网络要求高/访问慢)
- gitee网址:https://gitee.com/ (国内/访问速度快,推荐)
github注册,入门及如何提交代码:github注册,入门及如何提交代码_哔哩哔哩_bilibili
1. 大公司喜欢的东西
2. 慢慢扩展学习 git 教程链接 (不能只懂三板斧):Git教程 - 廖雪峰的官方网站 (liaoxuefeng.com)
3. 如何提交代码到 gitee 详细讲解:gitee(码云)的注册和代码提交【手把手】_哔哩哔哩_bilibili
4、Xmind思维导图整理技术框架体系,方便复习回顾
笔记作为一种辅助学习方法,做出对自己有效的笔记即可,xmind可以梳理出技术的整体框架,方便复习回顾,同时脑海里对于技术的掌握程度也会随着复习加深,学习的本质是重复!!!多思考,为什么?怎么做?需要注意什么?多总结,多实践!
5、保持刷题
刷题网站:力扣、牛客,剑指offer,从简单到中等再到难,分门别类,分题型,对于有收获的题、错误的题及时做好记录考察的本质和算法思想联想到它属于哪一类题型,从而构建自己的刷题体系,错误的题反复刷!多实践才会有提升!
6、如何学好C语言
做好上述的同时,需要注意:拒绝做伸手党,遇到问题,首先想到:发现问题、如何检索问题从而解决问题,学会学习,从而培养学习能力,解决问题的能力!
二、初识C语言
基本了解C语言的基础知识,对C语言有一个大概的认识。 每个知识点就是简单认识,不做详细讲解,后期博客会详细总结。
1、什么是C语言?
1.1 计算机语言是什么?
人类语言:人与人沟通的一种工具。例如:普通话、英语等。 计算机语言:人与计算机沟通的一种工具。我们可以编写代码让计算机完成某些功能。如:C,C++,Java,Python,Go,JavaScript 等。 语言 = 语法 + 逻辑
1.2 编程语言简史
第一代:机器语言:使用的是用二进制代码表示的语言,与人类语言差别极大,这种语言就称为机器语言,这种语言本质上是计算机能识别的唯一语言。
第二代: 汇编语言:使用英文缩写的助记符来表示基本的操作,这些助记符构成了汇编语言的基 础。比如:LOAD、MOVE 等,使人更容易使用。因此,汇编语言也称为符号语言。优点:能编写高效率的程序。 缺点:汇编语言是面向机器的,不同计算机会有不同的汇编语言,程序不易移植。
第三代: 高级语言:高级语言,是一种接近于人们使用习惯的程序设计语言。它允许程序员使用 接近日常英语的指令来编写程序,程序中的符号和算式也与日常用的数学式子差不多,接近于自然语言和数学语言,容易为人们掌握。高级语言独立于计算机硬件,有一定的通用性;计算机不能直接识别和执行用高级语言编写的程序,需要使用编译器或者解释器,转换为机器语言才能被识 别和执行。使用普遍的高级语言有 Fortran、ALGOL、Basic、COBOL、LISP、 Pascal、PROLOG、C、C++、VC、VB、Delphi、Java、JavaScript、python 等。
1.3 C语言的初步认识
1.4 C语言的版本发展
C语言是一种通用的计算机编程语言,能够让人和计算机进行交流,广泛应用于底层开发,二十世纪八十年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言制定了一套完整的美国国家标准语法,称为ANSIC,作为C语言最初的标准。
C语言是一门面向过程的计算机编程语言,与C++,Java等面向对象的编程语言有所不同。 其编译器主要有Clang、GCC、WIN-TC、SUBLIME、MSVC、Turbo C等。而学习使用的VS2019便是包含MSVC编译器的一种集成开发环境。
1.5 C语言的特点
C 语言是一种编译型语言,源文件都是文本文件,本身无法执行,必须通过编译器, 生成二进制的可执行文件,才能执行。 目前,最常见的 C 语言编译器是自由软件基金会推出的 GCC 编译器,可以免费使用。Linux 和 Mac 系统可以直接安装 GCC,windows 系统可以安装 MinGW。
MinGW 和 GCC 的区别: GCC 是一个跨平台的编译器集合,可用于多种操作系统和处理器架构,包括 windows; 而 MinGW 是 GCC 在 Windows 平台上的移植版本,主要用于在 Windows 上本地编译 C 和 C++代码。
1.6 C语言可在线使用的编译工具
当你在一台新设备上,没有安装 c 语言的编译器,没有 C 语言的 IDE 你还可以选择线 上 IDE。
CodingGround: https://tutorialspoint.com/compile_c_online.php
OnlineGDB: https://onlinegdb.com/online_c_compiler
Lightly:https://cde2f3ce.lightly.teamcode.com/
1.7 C语言运行的机制
过程 1:编辑 :编写 C 语言源程序代码,并以文件的形式存储到磁盘中。源程序文件以“.c”作为扩展名。(高级语言编写的文本文件)
过程 2:编译: 将 C 语言源程序转换为目标程序(或目标文件)。如果程序没有错误,没有任何提示, 就会生成一个扩展名为“.obj”的二进制文件。C 语言中的每条可执行语句经过编译后 最终都将被转换成二进制的机器指令。(机器语言的二进制文件)
过程 3:链接/连接:将编译形成的目标文件“.obj”和库函数及其他目录文件连接/链接,形成统一的可执行的二进制文件“.exe”。(最终可以执行的 exe 文件) 为什么需要链接库文件呢? 因为我们的 C 程序中会使用 C 程序库的内容,比如 、 中的函 数 printf()、system()等,这些函数不是程序员自己写的,而是 C 程序库中提供的,因此需要链接。链接后,生成的.exe 文件,比 obj 文件大了很多。
过程 4: 运行:有了可执行的 exe 文件,我们可以在控制台下直接运行此 exe 文件。
2、创建完整的项目流程
如何利用VS2019编写代码?
- 创建一个项目
- 创建源文件,包括头文件.h和源文件. cpp / .c
- 写代码
- 编译+运行代码
1.创建一个项目
2.创建源文件/头文件
后缀为.cpp编译器会按照c++的语法来编译代码,后缀为.c编译器会按照C的语法来编译代码!
3.编写代码
第一步首先写出主函数main(),如何执行呢? - C语言是从主函数的第一行开始执行的所以c语言代码中得有main函数-入口!有且仅有一个main函数 1.一个工程中可以有多个.c文件2.但是多个.c文件中只能有一个main函数!
4.编译+运行代码
编译+链接+运行代码快捷键: 1. ctrl+f5
2. fn+ctrl+f5
3.菜单中:【调试】->【开始执行不调试】
VS中存在的常见问题:
3、数据类型介绍
C语言为了描述各种不同类型的数据,同时为了高效的使用内存,定义出各种不同的数据类型,数据在计算机中的存储时存储的是二进制序列,内存划分的基本单位是1个字节,内存划分的最小单位为:1bit,同时1个字节等于8bit位(1byte=8bit),利用sizeof()关键字可以求得变量或者类型所占内存空间大小,单位为字节!
4、变量和常量
生活中的有些值是不变的(比如:圆周率,性别,身份证号码,血型等等) 有些值是可变的(比如:年龄,体重,薪资)。不变的值,C语言中用常量的概念来表示,变得值C语言中用变量来表示。
4.1 变量的定义
格式: 变量类型 变量名=初始化;(推荐变量定义时进行初始化,防止使用随机值变量)
4.2 变量的命名
- 只能由字母(包括大写和小写)、数字和下划线_组成。
- 不能以数字开头。
- 长度不能超过63个字符。
- 变量名中区分大小写的。
- 变量名不能使用关键字。
- 见名知意,使用驼峰标识!(首字母大写)
标识符命名建议 :
1. 在起名字的时候,“见名知意”。例如:sum,name,max,price等;
2. 不要出现仅靠大小写区分的变量名。例如 name 与 Name等多个单词组成时,采用小驼峰(myName)、大驼峰(MyName)等
3. 下划线通常用于连接比较长的变量名。如:max_classes_score等;
4.3 变量的分类
变量简单的可分为:局部变量、全局变量,局部变量指的是在函数体内部定义的变量(花括号里面),而全局变量指的是在函数体外部定义的变量(花括号的外面),当局部变量与全局变量同名时,优先使用局部变量!不建议二者同名。
4.4 变量的使用
当我们需要使用一个变量时,便进行定义同时初始化(局部变量未进行初始化为随机值!)。
#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:\n");
scanf("%d%d", &num1, &num2); //注意两点:原样输入,原样输出;后面是地址
sum = num1 + num2;
printf("sum = %d\n", sum);
return 0;
}
4.5 变量的作用域和生命周期
作用域
作用域(scope)是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用 的而限定这个名字的可用性的代码范围就是这个名字的作用域。简单来说就是可以使用的范围
1. 局部变量的作用域是变量所在的局部范围(花括号内部)。
2. 全局变量的作用域是整个工程。(其他文件使用需要加入关键字extern 声明变量)
生命周期
变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段
1. 局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
2. 全局变量的生命周期是:整个程序的生命周期。
说明:
1、全局变量在程序的全部执行过程中都占用内存单元,所以,不是必要情况,不建议使用全局变量;
2、使用全局变量过多,程序的可读性降低,程序不清晰。
3、当全局变量与局部变量重名的时候,在局部变量作用域范围内,局部变量起作用; 4、局部变量不能与局部变量重名;
5、全局变量不能与全局变量重名;
4.6 常量
C语言中的常量和变量的定义的形式有所差异。
C语言中的常量分为以下以下几种:
- 字面常量
- const 修饰的常变量
- #define 定义的标识符常量
- 枚举常量
#include <stdio.h>
#define MAX 100
//枚举常量举例
enum Sex
{
MALE,
FEMALE,
SECRET
};
//括号中的MALE,FEMALE,SECRET是枚举常量
int main()
{
/*第一种:字面常量
3.14;//字面常量
1000;//字面常量
"hello"//字符串常量*/
//第二种:const 修饰的常变量,本质上还是变量,只是不能被修改
const float pai = 3.14f; //这里的pai是const修饰的常变量
pai = 5.14;//是不能直接修改的!
const int n=10;
int arr[n]={0}; //错误,数组的长度只能是常量表达式
//第三种#define的标识符常量
printf("max = %d\n", MAX);
//枚举常量演示
printf("%d\n", MALE); //0
printf("%d\n", FEMALE);//1
printf("%d\n", SECRET);//2
//注:枚举常量的默认是从0开始,依次向下递增1的
return 0;
}
上面例子上的 pai 被称为 const 修饰的常变量, const 修饰的常变量在C语言中只是在语法层面限制了 变量 pai 不能直接被改变,但是 pai 本质上还是一个变量的,所以叫常变量。
5、字符串+转义字符+注释
5.1 字符串
"helloworld"这种由双引号引起来的一串字符称为字符串字面值,或者简称字符串。 注:字符串的结束标志是一个 \0 的转义字符。在计算字符串长度的时候 \0 是结束标志,不算作字符串内容。同时需要注意字符数组与字符串的区别!后期博客有总结。
常见问题1:是否存在'\0'结束标志!
常见问题2:strlen()函数求字符串长度!
#include <string.h>
#include <stdio.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = {'a','b','c','d','e','f','\0'};
char arr3[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr1));//6
printf("%d\n", strlen(arr2));//6
printf("%d\n", strlen(arr3));//随机值
return 0;
}
5.2 转义字符
转义字符就是转变原有字符的含义,成为另一种意思,需要注意转义字符当作一个字符!
#include <stdio.h>
int main()
{
//问题1:在屏幕上打印一个单引号',怎么做?
//问题2:在屏幕上打印一个字符串,字符串的内容是一个双引号“,怎么做?
printf("%c\n", "\'");
printf("%s\n", "\"");
return 0;
}
笔试题:
//程序输出什么?
#include <stdio.h>
int main()
{
printf("%d\n", strlen("abcdef"));
// \62被解析成一个转义字符
printf("%d\n", strlen("c:\test\628\test.c")); //14
return 0;
}
5.3 注释
作用:
1. 代码中有不需要的代码可以直接删除,也可以注释掉
2. 代码中有些代码比较难懂,可以加一下注释文字
注释方法:
第一种:行注释//
第二种:快注释 /* */
第三种:选中块按快捷键:ctrl +shift+/
第四种:代码注释:#if 0/1 #endif
6、控制语句之选择语句
多种选择分支
#include <stdio.h>
int main()
{
int coding = 0;
printf("你会去敲代码吗?(选择1 or 0):\n");
scanf("%d", &coding);
if(coding == 1)
{
prinf("坚持,你会有好offer\n");
}
else
{
printf("放弃,回家卖红薯\n");
}
return 0;
}
7、控制语句之循环语句
重复执行某一动作
C语言中如何实现循环呢?
- while语句-讲解
- for语句(后期总结)
- do ... while语句(后期总结)
//while循环的实例
#include <stdio.h>
int main()
{
printf("好好学习\n");
int line = 0;
while(line<=20000)
{
printf("我要继续努力敲代码\n");
line++;
}
if(line>20000)
printf("好offer\n");
return 0;
}
8、函数(重点!初步了解,后期详细总结)
在C语言中,函数是一种模块化和可重用的代码块,用于执行特定的任务。方便代码的复用。
8.1函数的定义:
返回类型 函数名(参数列表){
// 函数体
return 返回值;
}
- 返回类型:指定函数返回值的类型,可以是整数、浮点数、字符、指针等等。
- 函数名:函数的标识符,用于在程序中调用该函数。
- 参数列表:函数接受的输入,可以是0个或多个参数,参数之间用逗号分隔。
- 函数体:包含实际的代码块,执行特定的任务。
- 返回值:函数执行完毕后返回的值,如果没有返回值,函数的返回类型可以使用 “void”。
8.2 函数的声明:
在使用函数之前,通常需要提前声明函数,以便编译器知道函数的存在。
返回类型 函数名(参数列表);
(3)函数调用:
调用函数时,使用函数名和传递给函数的实际参数(实参),以相同类型的变量接收函数的返回值。
返回值变量 = 函数名(参数值);
#include <stdio.h>
// 函数声明
int add(int a, int b);
int main()
{
int result=0;
// 函数调用
int num1=3;
int num2=5;
result = add(num1, num2);
// 打印结果
printf("Sum: %d\n", result);
return 0;
}
// 函数定义
int add(int a, int b)
{
return a + b;
}
1、什么是实际参数和形式参数?
在函数调用过程中把函数的参数分为:形参和实参。
(1)形参(形式参数) :
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效 。(2)实参 (实际参数)
在调用有参函数时,函数名后面括号中的参数称为“实参”,是我们真实传给函数的参数,实参可以是: 常量、变量、表达式、函数等无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。2、实参和形参的关系?
虽然我们提到了形参与实参之间有联系,即实参是传递给形参的,但是形参和实参分别由自己独立的内存空间。这个现象可以通过调试进行观察,针对上面的代码看调试结果:
当我们运行程序,num1和num2(实际参数)的值通过调用add函数传递给了a,b(形式参数)。通过监视窗口我们可以看到即a,b接收了来自num1,num2的值,但是通过监视a,b和num1,num2的地址并不相同,即形参和实参分别有自己独立的内存空间,当实参传递给形参时,形参是实参的一份临时拷贝,对形参的修改是不会影响实参的。
9、数组(重点!初步了解,后期详细总结)
(1)数组的概念: 数组是相同数据类型的一组元素集合,在内存中占用一块连续的内存单元。
(2)数组的定义:数组元素类型 数组名 [数组长度]={初始化数据};
注意:数组部分初始化,则其余剩下元素为类型的默认值!
(3)数组的下标:C语言规定:数组的每个元素都有一个下标,下标是从0开始的。与指针偏移量有关!
(4)数组的访问/使用:数组有两种访问方式:数组下标或者指针均可!
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
for(int i=0; i<10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
10、操作符(重点!初步了解,后期详细总结)
(1)算术操作符
主要包括四个:+ 、 -、 * 、 / (得到的是商)、%(得到的是余数)
需要注意的地方:
1、大数据类型与小数据类型进行运算,计算结果为大数据类型;
2、高精度数据类型与低精度数类型进行运算,计算结果为高精度数据类型;
3、取余可以用来判断一个数是否是奇数还是偶数,即num%2是否等于0;
4、结合除法/和取余可以得到一个数的个位、十位、百位等
(整除10的结果相当于划掉原来数据的1位,对10取余相当于得到最后一位!);
(2) 移位操作符
主要有右移操作符和左移操作符,>> 、<<,移位操作符针对的是数据的二进制序列。
左移n位:相当于乘以2的n次方; 右移n位:相当于除以2的n次方;
(3)位操作符
主要有按位与、按位或、按位异或 ,&、| 、 ^ 针对的是数据的二进制序列。
(4) 赋值操作符
主要包括以下:=、 += 、-=、 *=、 /=、 &=、 ^=、 |= 、 >>= 、 <<=
(5) 单目操作符(只有一个操作数)
1、逻辑反 !
C语言规定:非0即为真,0即是假;
(1)常常用于if语句的条件判断时,只要条件不是0即为真 ,条件为0即是假; 如果一个数为0,取反则为真,结果为1!
(2)用法:
#include <stdio.h> int main() { int a = 0; printf("a进行取反:%d\n", !a); //结果为1 int b = 1; //实际开发两种用法 if (b) { 如果b为真,做事 } if (!b) { 如果b为假,做事 } return 0; }
2、sizeof()操作数的类型长度(所占内存空间的字节数)
用法:(1)sizeof()常常用于求一个数据类型或者变量所占的内存空间大小,单位为字节;
(2) sizeof()可用来求数组的长度 :int len=sizeof(arr)/sizeof(arr[0]);
#include <stdio.h> int main() { int a = 0; int arr[] = { 1,2,5,8,7,9,4,2,1,12,44,11,66,32 }; int len = sizeof(arr) / sizeof(arr[0]); printf("%d\n", sizeof(a)); //4 printf("%d\n", sizeof(int)); //4 printf("%d\n", sizeof(arr)); //56 printf("%d\n", sizeof(arr[0])); //4 printf("%d\n", sizeof(arr) / sizeof(arr[0])); //14 return 0; }
3、按位取反 ~
数据在计算机是以二进制形式存储的,按位取反是将一个数的二进制表示序列的所有位,1取反为0,0取反为1;
首先需要明确的是:int 整数占4个字节,一共32个比特位,最高位为符号位:0代表正数,1代表负数; 整数有三种表示形式:原码、反码、补码,在计算机中是以补码形式存放的,原码就是一个数的二进制表示形式,对于正整数来说,原码、反码、补码均相同,对于负整数来说,反码是在原码的基础上,符号位不变,其他位按位取反,补码是在反码的基础上加1。
%d打印的是对应原码的十进制!如果是负数,需要推到原码得到它的十进制数!
4、自增++和自减--操作符
这两种操作符分为前置和后置,并且针对的是变量的自增或者自减;
使用方法:
(1)单独构成一条语句,前置++和后置++结果相同(自减一样)
(2)不单独构成一条语句,和其他运算符结合使用或和函数传参结合使用时:
前置++(++num):先取num的值,自增1,后和其他运算符结合;
后置++(num++): 先取num的值,然后和其他运算符结合,后自增1;
5、(类型)强制类型转换
强制类型转换有四条规则:
(1) 大数据类型转换为小数据类型,必须显式转换(要有强制转换符号)
(2) 高精度数据类型转换为低精度数据类型,必须显式转换(要有强制转换符号)
(3) 小数据类型转换为大数据类型,不需要显式转换,编译器自动转换(空位用符号位填充)
(4)指针也可以强制转换,这与指针加1的能力有关!
(6)关系操作符
(7)逻辑操作符
注意:逻辑操作符是短路运算符,前面成立后面的则不会计算!
多个条件同时满足做某事,使用逻辑与,并且可以合理设置条件的顺序,实现相应的功能,多个条件满足一个即可做某事,使用逻辑或!
(8)条件操作符 (三目运算符)
表达式1?表达式2:表达式3
作用如下:表达式1成立,计算表达式2,整个表达式的结果是:表达式2的结果,如果表达式1不成立,计算表达式3,整个表达式的结果是:表达式3结果;
用法:可以用来替换双分支if...else...使代码更加简。
#include <stdio.h> int main() { printf("请输入两个数\n"); int a, b; int max; scanf("%d%d", &a, &b); if (a > b) { max = a; } else { max = b; } printf("这两个数中的较大数为:%d\n", max); return 0; } //另一种写法 #include <stdio.h> int main() { printf("请输入两个数\n"); int a, b; int max; scanf("%d%d", &a, &b); max=a>b?a:b; printf("这两个数中的较大数为:%d\n", max); return 0; }
(9) 逗号表达式
表达式1,表达式2,表达式3,...表达式N
用逗号隔开的一串表达式,逗号表达式,是从左向右依次计算的整个表达式的,结果是最后一个表达式的结果。
#include <stdio.h> int main() { int a=0; int b=3; int c=5; int d=(a=b+2,c=a-4,b=c+2); printf("%d\n",d); //3 return 0; }
(10)下标引用、函数调用和结构成员
(1)下标引用操作符 [ ]
访问数组元素采用的是下标,arr[i]便是下标引用操作符
(2)函数调用操作符()
调用函数时,函数名后面的()便是函数调用操作符
(3) .操作符
(4)->结构成员操作符
11、常见关键字
C语言规定关键字,自己不可以创建,并且变量的命名不能与关键字同名!!
11.1、关键字typedef
typedef 顾名思义是类型定义,这里应该理解为类型重命名。使用时在函数体外部。
(1)简单用法 : typedef对简单数据类型进行重命名
//将unsigned int 重命名为uint_32, 所以uint_32也是一个类型名
typedef unsigned int uint_32;
int main()
{
//观察num1和num2,这两个变量的类型是一样的
unsigned int num1 = 0;
uint_32 num2 = 0;
return 0;
}
(2)typedef对数组指针进行重命名
数组指针指的是指向一个数组整体的指针,此时指针加1代表,偏移整个数组大小的字节,对数组指针进行重命名,可以简化使用方式!
//解释数组指针
#include <stdio.h>
int main()
{
//定义一个包含3个元素的整型数组,注意下面两种写法一样,它的类型就是 int []
int arr[]={1,2,3};
int []arr={1,2,3};
//定义一个数组指针,指向整个数组,注意下面两种写法一样,它的类型就是 int [3]
//注意()和[]的顺序可以交换,由于[]的优先级高于(),因此,加上括号提高优先级(侧重点在于指针)
int [3] (*parr)=&arr; //
int (*parr) [3]=&arr;
return 0;
}
//利用typedef对数组指针进行重名,PARR是对int (*)[3]类型的重命名。PARR现在可以用来声明指向包含3个整数的数组的指针,而不必每次都写int (*)[3]。这样可以使代码更加清晰和易读。
#include <stdio.h>
typedef int(*PARR) [3]; //此时,PARR提升为类型名,代表这个类型int (*)[3]的重命名
int main()
{
int arr[]={1,2,3};
//利用类型重命名,定义指针变量
PARR p=&arr; //注意&不可以去掉,此时代表的是整个数组的大小,与指针加1的能力相关!
return 0;
}
(3)typedef对函数指针进行重命名
1、什么是函数指针?(深刻理解)
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数的所在内存区域的首地址,这和数组名非常类似,函数名就相当函数的入口地址,我们可以将函数名(入口地址)赋值给一个指针变量,使得指针变量指向函数所在的内存区域,然后就可以通过指针变量找到并且调用该函数,这种指针就是函数指针。
2、函数指针的定义形式
函数返回值类型 (*指针名称) (形参列表)
注意事项:
(1)第一个括号不可以省略,因为括号的优先级高于*!
(2)函数的参数列表可以同时给出参数类型和名称,也可以只给出参数类型!
//使用函数指针的方式来调用函数
#include <stdio.h>
//函数指针进行重命名,相当于对 int(*) (int x, int y)类型进行重命名为 Pfun
typedef int(*Pfun) (int x, int y); //或者typedef int(*Pfun) (int , int );
int add(int x,int y)
{
return x+y;
}
int mul(int x,int y)
{
return x*y;
}
int main()
{
int a=1,b=2;
Pfun p =add; //使用类型重命名的函数指针,定义一个函数指针,赋值给函数名;
int res=p(a,b); //使用这个函数指针进行函数调用p(a,b);
printf("%d\n",res);
return 0;
}
(4)typedef与结构体和结构体指针结合使用(适用.c文件也适用.cpp文件)
结构体是一种自定义数据类型,可以使用结构体定义一组不同数据类型的数据。
1、结构体的定义
结构体的定义格式如下:
struct 结构体名
{
成员列表(结构体所包含的变量或数组或结构体或指针)
};//此时,结构体名就是类型名
2、结构体变量的定义
在.c文件中:struct 结构体类型名 变量名; 如:struct student s; (必须要加关键字struct)
在.cpp文件中:结构体类型名 变量名; 如:student s;(不用加关键字struct)
3、结构体指针变量的定义
当一个指针变量指向结构体时,称之为结构体指针,定义方式如下:
在.c文件中:struct 结构体类型名 * 结构体指针变量名=&结构体变量;
如:struct student * ps=&s;
在.cpp文件中:结构体类型名 * 结构体指针变量名=&结构体变量;
如: student * ps=&s;
4、为了方便既能在.c文件也能在.cpp文件中使用,利用类型重命名,对二者进行整合,方便实际开发。
//结构体类型重命名和结构体指针变量类型重命名 typedef struct Student { int id; int age; } Student,*Ps; //相当于如下代码: typedef struct Student Student; typedef struct Student * Ps; //使用类型重命名后的来定义结构体变量和结构体指针变量 int main() { Student s={11,15}; Ps p = &s; return 0; }
11.2 关键字static和extern(重点!!!)
在C语言中: static是用来修饰变量和函数的
1. 修饰局部变量-称为静态局部变量;
2. 修饰全局变量-称为静态全局变量;
3. 修饰函数-称为静态函数;
extern:用来声明外部符号。
1、修饰局部变量-静态局部变量
static修饰局部变量:本质上影响了变量的存储类型,一个局部变量是存储在栈区的,但是被static修饰后就存储在静态区了,因为存储类型的变化,生命周期就跟着变化了。
但是其作用域不变,仍为所在函数的范围。
//代码1:正常使用的局部变量 #include<stdio.h> void test() { int k = 1; k++; printf("%d ", k); } int main() { int i = 0; for(i=0;i<5;i++) { test(); } return 0; }
运行结果:
为什么运行结果是2 2 2 2 2呢,因为变量k是局部变量,它的作用域在test所在的范围内,我们在main函数里调用test函数时每调用一次,k就会被创建一次,同样的在test函数结束时,k变量随即被销毁,下次进入循环时k又被创建,所以每次输出的k值都为2。
//代码2:static修饰的局部变量 #include<stdio.h> void test() { static int k = 1;\\使用static修饰局部变量k k++; printf("%d ", k); } int main() { int i = 0; for(i=0;i<5;i++) { test(); } return 0; }
运行结果:
结果为2 3 4 5 6,因为我们使用static修饰局部变量k改变了它的生命周期,此时变量k存储在静态区而非栈区,它的生命周期和程序一样长,因此在test函数结束时,局部变量k不会被系统销毁,所以经过五次循环它的值依次增加,最终结果为2 3 4 5 6。
2、修饰全局变量-静态全局变量
static修饰全局变量,使得这个全局变量只能在自己所在的源文件 (.c)内部可以使用其他源文件不能使用!
全局变量,在其他源文件内部可以被使用,是因为全局变量具有外部链接属性但是被static修饰之后,就变成了内部链接属性,其他源文件就不能链接到这个静态的全局变量了!(1)正常使用全局变量
我们在add.c文件中定义一个全局变量g_val,在test.c文件中使用extern进行外部声明后打印出g_val的值,程序成功运行并打印出val的值2022,这便是全局变量的外部链接属性,即可以被其他文件使用(需要进行extern外部声明后使用)。
(2)被static修饰的全局变量
当我们使用static修饰后,运行程序会发现程序报错,error:无法解析的外部符号g_val,这就是上面提到的,使用static修饰全局变量会将其外部链接属性变为内部链接属性,相当于全局变量g_val的作用域被缩小了。
3、static修饰函数
static修饰函数,使得函数只能在自己所在的源文件内部使用,不能在其他源文件内部使用本质上: static是将函数的外部链接属性变成了内部链接属性! (和static修饰全局变量一样!)
(1)正常使用其他文件的函数
(2)被static修饰的函数
12、define 定义常量和宏
//第一种使用方法:define定义标识符常量
#define MAX 1000
//第二种使用方法:define定义宏
#define ADD(x, y) ((x)+(y)) //加括号是因为宏本质上完成的是宏替换
#include <stdio.h>
int main()
{
int sum = ADD(2, 3);
printf("sum = %d\n", sum);
sum = 10*ADD(2, 3); //#define ADD(x, y) x+y 如果是这种写法结果不会是50,而是23
printf("sum = %d\n", sum);
return 0;
}
13、指针(重点!初步了解,后期详细总结)
13.1 理解内存和地址
13.1.1 内存
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。其实内存的使用和现实生活中的空间使用本质上是一样的,设想有个生活中的案例:假设有一栋宿舍楼,把你放在楼里,楼上有100个房间,但是房间没有编号,你的一个朋友来找你玩,如果想找到你,就得挨个房子去找,这样效率很低,但是我们如果根据楼层和楼层的房间的情况,给每个房间编上号,如:
生活中,每个房间有了房间号,就能提高效率,能快速的找到房间。
如果把上面的例子对照到计算中,又是怎么样呢?
我们知道计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何高效的管理呢? 那么我们就需要明白两个问题:
1、内存是怎么编号的?
2、一个内存单元是多大的空间?
我们目前微软的操作系统有32位(x86)和64位的,这里以32位为例,进行说明,32位代表有32根地址总线,本质上是物理线,由通电和断电进行表示(0/1),因此电信号便可以转化成数字信号(信息),即32位0或者1组成的二进制序列(占据4个字节),因此便会有2的32次方个组合,这也代表有2的32次方个内存单元!
总结:
内存划分为一个个的内存单元,每个内存单元的大小取1个字节。其中,每个内存单元,相当于一个学生宿舍,一个人字节空间里面能放8个比特位,就好比学生们住的八人间,每个人是一个比特位。每个内存单元也都有一个编号(这个编号就相当于宿舍房间的门牌号),有了这个内存单元的编号,CPU就可以快速找到一个内存空间。生活中我们把门牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字叫:指针。指针是一个变量,它是用来存放地址的!使用指针,其实是使用指针存放的地址,因此指针与地址等价!
⭐所以我们可以理解为:
内存单元的编号==地址==指针
13.1.2 如何理解编址?
CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址(就如同宿舍很多,需要给宿舍编号一样)。计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。钢琴、吉他上面没有写上“都瑞咪发嗦啦”这样的信息,但演奏者照样能够准确找到每一个琴弦的每一个位置,这是为何?因为制造商已经在乐器硬件层面上设计好了,并且所有的演奏者都知道。本质是一种约定出来的共识!
硬件编址也是如此
首先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要互相协同工作的。所谓的协同,至少相互之间要能够进行数据传递。但是硬件与硬件之间是互相独立的,那么如何通信呢?答案很简单,用"线"连起来。而CPU和内存之间也是有大量的数据交互的,所以,两者必须也用线连起来。不过,我们今天关心一组线,叫做地址总线。我们可以简单理解,32位机器有32根地址总线,每根线只有两态,表示0,1【电脉冲有无】,那么一根线,就能表示2种含义,2根线就能表示4种含义,依次类推。32根地址线,就能表示2^32种含义,每一种含义都代表一个地址。地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器。
13.2 指针变量和地址
13.2.1 取地址操作符 (&)
#include<stdio.h>
int main()
{
int a = 10; //a在内存中要分配空间,占用4个字节的空间
&a;//取出a的地址
printf("%p\n", &a); //%p是专门用来打印地址的!
return 0;
}
按照我画图的例子,会打印处理:006FFD70,&a取出的是a所占4个字节中地址较小的字节的地址。虽然整型变量占用4个字节,我们只要知道了第一个字节地址,顺藤摸瓜访问到4个字节的数据也是可行的。
13.2.2 指针变量和解引用操作符 (*)
1. 指针变量
那我们通过取地址操作符(&)拿到的地址是一个数值,比如:0x006FFD70,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?
答案是:指针变量中
#include <stdio.h>
int mian()
{
int num=10;
int *p=# //p是用来存放地址的,在C语言中叫指针变量
return 0;
}
2. 拆解指针类型
int num=10;
int *p=#左边是 int *,*代表这是一个指针变量,int代表p指向的是整型类型的数据!
3. 解用操作符*
我们将地址保存起来,未来是要使用的,那怎么使用呢?在现实生活中,我们使用地址要找到一个房间,在房间里可以拿去或者存放物品C语言中其实也是一样的,我们只要拿到了地址(指针),就可以通过地址 (指针)找到地址()指向的对象,这里必须学习一个操作符叫解引用操作符(*)。
#include<stdio.h>
int main()
{
int a = 10;
int *pa = &a;//将取出的地址存放在p中
*pa = 0; //解引用操作,通过pa里面存放的地址找到a,是一种间接访问方法
return 0;
}
上面代码中第6行就使用了解引用操作符,*pa的意思就是通过pa中存放的地址,找到指向的空间,*pa其实就是a变量了;所以*pa=0,这个操作符是把a改成了0。
有同学肯定在想,这里如果目的就是把a改成0的话,写成a = 0;不就完了,为啥非要使用指针呢?其实这里是把a的修改交给了pa来操作,这样对a的修改,就多了一种的途径,写代码就会更加灵活,后期慢慢就能理解了。
13.3 指针变量大小
前面的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产生的2进制序列当做一个地址,那么一个地址就是32个bit位,需要4个字节才能存储。
如果指针变量是用来存放地址的,那么指针变的大小就得是4个字节的空间才可以。
同理64位机器,假设有64根地址线,一个地址就是64个二进制位组成的二进制序列,存储起来就需要8个字节的空间,指针变的大小就是8个字节。
#include <stdio.h>
//指针变量的大小取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
printf("%d\n", sizeof(char *));
printf("%d\n", sizeof(short *));
printf("%d\n", sizeof(int *));
printf("%d\n", sizeof(double *));
return 0;
}
结论:
32位平台下地址是32个bit位,指针变量大小是4个字节.64位平台下地址是64个bit位,指针变量大小是8个字节。注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
14、结构体(重点!初步了解,后期详细总结)
14.1 结构体的理解
结构体的存在使得C语言可以创建自己定义的数据类型,称为自定义类型,可以存放一组不同类型的数据。
14.2 结构体类型的定义、创建和初始化
#include <stdio.h>
struct Student //结构体类型的定义,在函数的外部,关键字struct
{
char name[20];
int age;
double score;
}; //分号不要少
int main()
{
struct Student s={"张三",20,95}; //结构体的创建和初始化,花括号赋值
return 0;
}
14.3 结构体的三种访问方式
以上就是初识C语言的内容,更加详细的总结见后期博客!欢迎大家点赞加关注评论,您的支持是我前进最大的动力!下期再见!