栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【C语言学习笔记】从hello world开始,带你入门C语言

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【C语言学习笔记】从hello world开始,带你入门C语言

从hello world开始,入门C语言

文章目录
  • 从hello world开始,入门C语言
    • 0x00 从hello world开始
    • 0x01 数据类型和常量变量
      • 1、数据类型
      • 2、变量的定义
      • 3、变量的分类
      • 4、变量的使用
      • 5、常量
    • 0x02 字符串和转义字符
      • 1、字符串
      • 2、转义字符
    • 0x03 选择语句和循环语句
      • 1、选择语句
      • 2、循环语句
    • 0x04 函数
    • 0x05 #define定义常量和宏
      • 1、定义常量
      • 2、定义宏
    • 0x06 数组
    • 0x07 指针
      • 1、内存
      • 2、指针变量的定义
      • 3、指针变量的大小
    • 0x08 结构体
    • 0x09 操作符
    • 0x0A 关键字
      • 1、typedef
      • 2、static

0x00 从hello world开始
#include

int main()
{
    //第一个C程序
	printf("hello world!n");
    
    return 0;
}

上面这个程序相信大家都不陌生吧,这就是C语言诞生之初的第一个C程序,它非常简单,但能够引领我们走进计算机世界的大门,感受编程和技术之美。麻雀虽小,五脏俱全,那我们从上面这段简单的代码中能够得到什么呢?

解析
#include是一个预处理指令,用来引入头文件,会告诉计算机在编译之前要包含stdio.h头文件
stdio.h是一个标准输入输出头文件(Standard Intput and Output.head),里面就包括printf()函数
main()是主函数,是C程序的入口,代码从main()函数开始执行,所有的C程序都需要main()函数
//是注释说明的符号,也可以是这种风格:,,不会参与编译
printf()是格式化输出到屏幕的函数,该函数在"stdio.h"头文件中声明
return 0;是子程序返回语句,用在main()函数中是C程序的出口,,终止程序并返回0

柿子先挑软的捏,这里说一下注释:

  1. 在我们写代码的过程中,有不需要的部分可以直接删除,也可以将其注释掉
  2. 而有些代码语句比较难懂,为了便于理解与后期维护,可以适当添加注释,这一点非常重要
 

注释有两种风格:

  • C语言风格:使用,格式为,它的缺点是需要成对出现,且不能嵌套注释
  • C++风格:使用// ,格式为//注释内容,相比与上面一种,它简单方便,可以注释一行也可以注释多行


0x01 数据类型和常量变量 1、数据类型

一个C程序的基本结构如下:

  • 预处理指令
  • 函数
  • 变量
  • 语句和表达式
  • 注释

显然,在我们的第一个C程序"hello world"中并没有出现变量。在认识变量之前,我们可以先谈谈C的数据类型。

首先我们要知道计算机并不像我们这么优秀,不仅可以说一口流利的外语,还能来时不时地整上一段 炏謃忟 ,它是九年义务教育的漏网之鱼,无法理解复杂的东西,只能看懂0和1,也就是二进制的东西,让我们从最简单的整数说起,看看计算机是如何处理它的。

当计算机需要处理整数时,它会先在内存中开辟一块空间,用来存放整数,并给他一个名字,方便以后查找和使用。
比如语句int a;就是使用了C语言中的关键字int(integer的缩写:意思是整数)在内存中开辟了一块空间a(名字是a,肉麻一点也可以是sweetheart,爬满老茧的手居然可以敲出这么温柔细腻的代码,爱了爱了)

看到这条语句我们就能够知道:程序在内存中开辟了一块空间,并命名为a,用它来存放整数,我们在使用时,只需要提供变量名a即可;那么C语言能不能存放小数呢,作为一门计算机高级语言,它的能力是毋庸置疑的!下表罗列了C语言中基本的数据类型,其中的浮点型就是用来描述小数的:

关键字数据类型数据长度/字节
char字符型1
short短整形2
int整形4
long长整形4
float单精度浮点型4
double双精度浮点型8

1、这么多数据类型有什么意义呢?

如果我们要使用计算机程序解决生活中的问题,首先得有描述问题的能力,把问题抽象化,变成程序模型,然后通过有效的输入得到我们想要的输出,因此这么多的数据类型就是让计算机能够尽可能准确地描述问题,从而把问题模型抽象化,最终通过程序来解决问题。

2、每种类型的大小是多少?

上表中给出了在32位平台下每种数据类型所占用的字节大小,我们暂时先知道这些即可,后期再继续探讨。


2、变量的定义

那么我们知道了这些数据类型,怎么去定义变量呢?
其实上文中出现的int a;就是一个定义了整形变量a的语句,我们结合下面的代码再简单分析一下:

#include

int main()
{
	char c='a';
    int i=10;
 	float f=0.618;
    
    return 0;
}

char定义了一个字符型变量c,并使用"="赋值为字符’a’;

int定义了一个整形变量i,并赋值为10;

float定义了一个单精度浮点型变量f,并赋值为0.618;

如何定义变量:数据类型+变量名+变量初始化(赋值),当然也可以不初始化,那么定义的变量里就会是一个随机值。


3、变量的分类

除了数据类型的区别,变量又分为

  • 局部变量

  • 全局变量

#include

int year=2020;//全局变量
int main()
{
	int year=2022;//局部变量
    printf("%dn",year); //%d是以有符号的十进制整形的形式打印
    
    return 0;
}

程序执行结果如下:

2022

感觉是变量重复定义了,但其实没有问题,当局部变量和全局变量同名时,局部变量优先使用

这里再引入两个概念——变量的作用域和生命周期

作用域:

程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的

而限定这个名字的可用性的代码范围就是这个名字的作用域

  1. 局部变量:作用域是变量所在的局部范围,比如说某个函数内部定义的变量,出了函数就无法使用了
  2. 全局变量:作用域是整个工程,在不同的文件中需要重新声明,但都可以使用

生命周期:

变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段

  1. 局部变量:进入作用域,生命周期开始,出作用域生命周期结束
  2. 全局变量:整个程序的生命周期

4、变量的使用

变量定义后,我们又该如何使用呢,其实上文也提到过。当我们知道变量存储在哪里,以及它的数据类型后,我们就可以在它的作用域内合理使用,只需要提供变量名即可。

#include

int main()
{
    int a=10;
    int b=20;
    int c=0;
    c=a+b;
    printf("%dn",c);
    
    return 0;
}

程序执行结果如下:

30

这是一段简单的代码,可以实现两个整形变量相加,并把结果以%d(有符号的十进制整形)的形式打印在屏幕上。


5、常量

常量就是常规变量,在定义后不能被修改,它可以是任何基本的数据类型,一般分为以下几种:

  • 字面常量(包括字符常量,整数常量,浮点常量等)
  • 枚举常量

C语言中,定义常量的方式通常是

  • 使用const关键字
  • 使用#define预处理器

放段代码简单演示一下:

#include
//定义枚举常量
enum Color
{
    RED,
    YELLOW,
    BLUE,
    GREEN
};
int main()
{
    //字面常量
    'x';
    1.414;
    10000;
    
    //使用枚举常量
    printf("%d %d %d %dn",RED,YELLOW,BLUE,GREEN);
    
    //const 修饰的常变量
    const float pi=3.14f;//这里pi是const修饰的常变量
    pi=3;//无法修改
    
    //#define的标识符常量
    #define MAX 100
    printf("max=%dn",MAX);
    
    return 0;
}

枚举常量默认是从0开始的,会依次向下递增1,RED=0,YELLOW=1,BLUE=2,GREEN=3。

在给pi赋值时,会报错:表达式必须是可修改的左值;浮点型变量pi在定义时被const修饰了,因此无法修改,是在语法层面上限制了它,但pi本质上还是一个变量,所以称之为常变量。



0x02 字符串和转义字符 1、字符串

字符串字面值或常量是括在双引号里面的,一个字符串包含类似于字符常量的字符:普通字符、转义字符和通用字符,比如字符串“hello world!n”。

[备注]:在C语言中,字符串实际上是使用空字符结尾的一维字符数组,因此是字符串的结束标志,不算作字符串内容,它会告诉编译器,这并非字符0,而是整个字符串到此为止。

我们看一下下面这段代码,打印结果是什么?(程序运行环境为VS2019)

#include

int main()
{
    char arr1[]="CSDN";
    char arr2[]={'C','S','D','N'};
    char arr3[]={'C','S','D','N',''};
    printf("%sn",arr1);//%s是以字符串的形式打印
    printf("%sn",arr2);
    printf("%sn",arr3);
    
    return 0;
}

程序执行结果如下:

CSDN

CSDN烫烫烫烫烫烫烫烫烫烫烫烫烫烫CSDN

CSDN

真的有这么烫吗???

第二个字符串在打印时到底发生了什么?(在不同的编译器下运行,乱码情况略有区别)

  • 因为在arr2中并没有字符串结束标志,在printf()函数调用过程中,它会继续往下打印直到遇到;

  • 需要注意的点是:在定义字符串arr1时,双引号里面会自动帮我们补上。
    因此我们在创建字符串时,最好使用双引号的方式,如果使用{}进行赋值,需要手动加上,这是我们容易忽略的。

字符串的本质其实是字符数组,这里贴个arr1的图方便大家理解,有关数组的知识,等下我们会说到。


2、转义字符

上面的代码让我们知道了转义字符的重要性,在我们写代码的过程中不能忽略,除了,还有哪些转义字符呢?比如我们要在屏幕上打印一个目录:D:testcodetest.c 该如何写代码呢?(程序运行环境为VS2019)

#include

int main()
{
    printf("D:testcodetest.cn");

    return 0;
}

程序执行结果如下:

D: estcode est.c


显然程序并没有输出我们想要的结果,这里就需要了解转义字符的知识,在C中,有一些特定的字符,当它们前面有反斜杠时,就具有特殊含义:

转义序列释义
\字符,表示反斜杠,防止被理解为转义字符
’表示字符常量'
""字符,可以用来表示字符串内部的双引号
??字符
a警报铃声
b退格键
f换页符
n换行符
r回车
t水平制表符
v垂直制表符
oooooo表示一到三位的八进制数,如130
xhh…hh…表示一个或多个数字的十六进制数xff

现在我们修改一下刚才的代码,在水平制表符t前加上一个反斜杠,让它变成原来的意思,再运行一遍:

#include

int main()
{
    printf("D:\testcode\test.cn");

    return 0;
}

程序执行结果如下:

D:testcodetest.c


再看下面这段代码,输出是什么呢?

#include  
#include 

int main() 
{
	printf("%dn", strlen("abcdef"));  
    printf("%dn", strlen("c:test628test.c")); 
    
    return 0;
}

程序执行结果如下:

6

14

其中strlen是包含在头文件string.h里的函数,用来计算字符串长度,现在我们来分析一下:

第一个比较简单,字符串abcdef,一共六个字母,所以是6没错

第二个为什么是14呢?我们刚刚学了有关转义字符的知识,每个转义字符都算作一个字符,画一个图看看。

其中的t是水平制表符,62是八进制数,是自动添加的字符串结束标志,因此第二个字符串长度是14。

除此之外,我们还要知道的一个点是,每个字符都有对应的ASCII码值,而每个ASCII码用八位二进制数表示,占两个字节(Byte),因此对字符来说,最大的二进制数1111 1111,转换为其他进制就是:

二进制(BIN_binary)八进制(OCT_octal)十进制(DEC_decimal)十六进制(HEX_hexadecimal)
1111 11113772550xff
  • 转义序列中用八进制和十六进制表示一个字符时,八进制不超过377与十六进制不超过xff,如用八进制数666(十进制是438)表示一个字符,编译器会报错:"438"对字符来说太大;
  • 如果要表示十六进制数,那么x不能省略,如果省略,编译器不会把他当作十六进制数处理。


0x03 选择语句和循环语句 1、选择语句

上文中的代码都是依次往下执行的语句,这种就是最简单的顺序结构,但是这并不能够很好地解决问题。

比如说交通,过马路的时候遇到红灯我们选择停下,绿灯我们安全通过,对应到程序上就是选择语句。

再比如说,从现在开始我们好好学习,用不了多久就会升职加薪,当上总经理,出任CEO,迎娶白富美,走上人生巅峰,反之,如果我们躺平摆烂什么都不干,就会变成废物一个。。。。

#include

int main()
{
    int choice=0;
    printf("是否好好学习?输入1代表是,0代表否n");
    scanf("%d",&choice);
    if(choice==1)
    {
        printf("走上人生巅峰n");
    }
    else
    {
        printf("堂堂正正的废物n");
    }
    
    return  0;
}

其中scanf是包含在头文件stdio.h的函数,用来读取输入。


2、循环语句

有些事需要我们日复一日的坚持去做,对应到程序上就是循环语句,我们这次拿读书举例子:

C语言中实现循环的方式有以下三种:

  • while语句
  • do…while语句
  • for语句

它们各有特点,这里先用while语句写一个实现循环的代码,简单认识一下,后面会对他们做详细的讲解:

#include

int main()
{
	int num=666;
    while(num<10000)
    {
        num++;
        printf("读书破&d卷n",num);
    }
    if(num>10000)
    {
        printf("下笔如有神n");
    }
    
    return 0;
}

程序执行结果这里就不再展示,感兴趣的小伙伴可以自行尝试。



0x04 函数

我们将变量的定义中用到的代码拷贝过来,如果说要把它写成一个加法函数,该怎么做呢?

#include

int main()
{
    int a=10;
    int b=20;
    int c=0;
    c=a+b;
    printf("%dn",c);
    
    return 0;
}

比较简单,代码如下:

#include

int Add(int x,int y)
{
	return x+y;
}

int main()
{
    int a=10;
    int b=20;
    int c=0;
    c=Add(a,b);
    printf("%dn",c);
    
    return 0;
}

上面我们将代码封装成函数的过程就是函数的定义:

在调用函数时,我们需要知道函数名,函数在使用时带(),括号内是函数的参数,也可以没有参数(无参函数),函数调用后,会执行函数体里的语句,在处理完数据后,会得到一个处理结果,就是返回值,因此一个函数由函数名、参数、函数体和返回类型组成。

  • 函数名是标识符的一种,命名规则与标识符相同;

  • 函数的参数可以数据或变量,一个函数可以有多个参数;

  • 函数体需要要使用大括号{},将里面所有的语句封装;

  • 如果函数有返回值,使用return语句返回,return语句后面的数据类型要和函数的返回类型保持一致;

  • 如果函数无返回值,定义时需要使用void作为函数返回类型。

上面的代码中:

int Add(),int是函数的返回类型,返回一个整形,Add是函数名,符合C语言标志符命名规则;

Add(int x,int y),x和y是Add函数的参数,类型是int;

{return x+y;},函数体内的语句return x+y;是返回参数x和y相加后的结果,与返回类型一致;


乍看之下,在主函数之外,另外写一个函数来处理加法好像让代码变得更长了。

试想一下,如果我们要处理成千上万个加法运算呢,那么函数的优点就显而易见了。

就像我们调用包含在头文件里的库函数一样,不会让我们从造轮子开始写程序。

因此写代码过程中我们可以将常用功能模块化,封装成函数,这样能够提高效率,更好地解决问题。

函数的优点:

  • 简化代码,将程序功能模块化

这里只是简单介绍一下函数,有关于函数部分后续我也会一一学习,整理好了再把笔记上传。



0x05 #define定义常量和宏

一个C程序中包括了预处理指令,这里我们简单介绍一下预处理指令#define,它有哪些作用呢?

  • 定义常量
  • 定义宏
1、定义常量

在最开始介绍数据类型和常量变量那里我们有提到使用#define定义常量,这里我们补充说明一下。

#define NUM 100

#include

int main()
{
    int a=100;
    int b=400;
    int	c=0;
	c=a+NUM;
    printf("%dn",c);
    c=b-NUM;
    printf("%dn",c);
    
    return 0;
}

程序执行结果如下:

200

300

  • #define NUM 100这条预处理指令会告诉计算机,在编译时将所有的NUM替换为100;

  • 使用#define定义常量可以增强代码的可读性,也便于后期修改与维护代码。


2、定义宏

在介绍函数时我们将加法封装成函数,在程序中调用。我们也可以定义宏,实现和加法函数相同的功能。

#define ADD(x,y) ((x)+(y))
#define MAX(x,y) (((x)>(y))?(x):(y))

#include

int main()
{
    int a=10;
    int b=20;
    int sum=0;
    int max=0;
    sum=ADD(a,b);
    max=MAX(a,b);
    printf("sum = %dn",sum);
    printf("max = %dn",max);
    
    return 0;
}

程序执行结果如下:

sum = 30

max = 20

上述代码中的宏一个是加法,一个是取最大值,ADD和MAX是宏的名称,他们都有参数,在使用时需要注意:

  • 必须使用#define定义;
  • 程序中反复使用的表达式就可以使用宏定义;
  • 宏名是标识符的一种,命名规则与变量相同;
  • 程序中出现的宏名,在预处理阶段会进行宏替换;
  • 参数列表是括在圆括号里的,必须紧跟在宏名称的后面,不能出现空格,否则编译器会报错。


0x06 数组

上文中有关字符串的介绍中有提到数组的概念,C语言中,把一组数据类型相同的元素集合称为数组。

我们知道,如果要把数据放入内存中,就必须要分配内存空间,比如放入1~10,计算机就得分配10个int类型的内存空间:

int arr[10]={1,2,3,4,5,6,7,8,9,10};//定义一个整形数组,最多放十个元素

而我们要访问这个数组中的某个元素,可以用数组的下标来实现C语言中规定:数组的每个元素都有下标,下标是从0开始的。

如何访问数组中的成员呢?下面我们使用一个简单的for循环:

#include

int main()
{
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    int i=0;
    for(i=0;i<10;i++)
    {
        printf("%d ",arr[i]);
    }
    
    return 0;
}

程序执行结果如下:

1 2 3 4 5 6 7 8 9 10

上面的代码,是从0的下标开始依次打印数组arr中的每个元素,用数组名+[下标]的方式访问数组元素,每次打印完后,下标自增+1,继续往后访问下一个数组元素。



0x07 指针 1、内存

程序的运行都是在内存中完成的,而为了有效的使用内存,就把内存划分成了一个个小的内存单元,每个内存单元的大小是1个字节,为了访问内存中的所有单元,我们对其逐一编号,并将内存中的每个字节的编号称为地址或者指针,地址从0开始依次增加。对于32位环境,最小的地址为0x00000000,最大的地址为0xFFFFFFFF。

我们在程序中任意定义一个变量,就会在内存中开辟一个空间用来存放它,它是有地址的,那么如何取出变量的地址呢?请看下面的代码:

#include

int main()
{
    int a=10;
    &a;
    printf("%pn",&a);
    
    return 0;
}

程序执行结果如下:

007EFE9C

  • &是取地址操作符,&a是取出整形变量a的地址;

  • 整形变量a在内存空间中占4个字节,每个字节都有地址,这里取出的是第一个字节的地址(较小的地址);

  • %p是以十六进制的形式打印,地址是用十六进制数表示的,因此打印出了整形变量a在内存中的地址。


2、指针变量的定义

变量的地址我们刚刚已经取出了,又该如何存储它呢,这里我们需要定义指针变量:

#include

int main()
{
    int a=10;
    int *p;
    p=&a;
    *p=20;
    printf("a = %dn",a);
    
    return  0;
}

程序执行结果如下:

a = 20

这里我们假设整形变量a的地址是0x007EFE9C,那么程序执行流程如下:


  • 定义指针变量与定义普通变量的方式十分相像,但要在变量名前面加*,代表它定义的是指针变量;

    指针变量也可以连续定义int *p,*q,*r;,这里的p、q、r类型都是int*;

    如果这样写int *p,q,r;,那么只有p是指针变量,指向一个整形,q和r都是普通的整形变量。

  • int *p;,其中int*是变量类型,表示指向整形而非其他类型的一个指针变量,p是该指针变量名;

  • 指针变量可以在定义时直接初始化赋值,如int *p=&a;,也可以在使用时赋值,如int *p;,p=&a;;

  • *是解引用操作符,*p是解引用访问指针变量p所指向的整形变量a,*p和a等价,*p=20;就等价于a=20;

[注意]:我们在定义指针变量时必须带*,而给指针变量本身赋值时不能带*,如果赋值时带*,就是解引用了。

以整形指针为例,可以推广到其他类型,如字符指针:

#include

int main()
{
    char c='q';
    char *pc=&ch;
    *pc='w';
    printf("%cn",c);
    
    return 0;
}

3、指针变量的大小

指针变量的大小与指针类型无关,取决于地址的大小。

32位平台下地址是32个bit位,即4个字节

64位平台下地址是64个bit位,即8个字节

在计算机内存中,最小的单位是1个比特bit或者1位(二进制位),每8个比特位就是1个字节Byte,内存单位的换算关系如下:

  • 1Byte = 8 Bit
  • 1KB = 1024Byte = 210Byte
  • 1MB = 1024KB = 220Byte
  • 1GB = 1024MB = 230Byte
  • 1TB = 1024GB = 240Byte
  • 1PB = 1024TB = 250Byte
#include

int main()
{
    printf("%d ",sizeof(char*));
    printf("%d ",sizeof(short*));
    printf("%d ",sizeof(int*));
    printf("%d ",sizeof(float*));
    printf("%d ",sizeof(long*));
    printf("%d ",sizeof(double*));
    
    return 0;
}

运行在x86,32位平台下:

4 4 4 4 4 4

运行在x64,64位平台下:

8 8 8 8 8 8

sizeof是C语言中的一个关键字,我们可以将sizeof()理解为一个操作符,它的操作数可以是变量名,也可以是类型名,它的功能是计算操作数在内存中所占用的字节大小。
后续的学习中我们还会遇到,到时再总结一波它的用法和注意事项,这里不再详细讲解。


0x08 结构体

C语言中结构体的存在给它加了BUFF,使得它具备了描述复杂问题的能力。

比如描述学生,学生包含:名字+年龄+性别+学号 这几项信息,显然这不是用基本数据类型(char、int、float等)所能描述清楚的,只能使用结构体:

#include

struct Stu
{
    char name[20];
    int age;
    char sex[4];
    char id[20];
};

int main()
{
    struct Stu s={"张三",21,"男","2022211177"};
//.访问结构体成员    
    printf("name = %sn",s.name);
    printf("age = %dn",s.age);
    printf("sex = %sn",s.sex);
    printf("id = %sn",s.id);
//->操作符    
    struct Stu *ps=&s;
    printf("name = %sn",ps->name);
    printf("age = %dn",ps->age);
    printf("sex = %sn",ps->sex);
    printf("id = %sn",ps->id);
    
    return 0;
}

程序执行结果这里不再展示,感兴趣的小伙伴可以自行尝试。

struct Stu{};是定义结构体的语句:

  • strcut是C语言中的关键字,用来声明结构体类型;
  • Stu是结构体名;
  • {}用来放结构体成员,里面可以包含多个变量或数组,类型可以相同,也可以不同,每个这样的变量或者数组都被称为这个结构体的成员;
  • {}后面的分号;是必须的,不能省略,因为定义结构体本身就是一条完整的语句。

上面的代码中我们定义的结构体里就包含了4个成员,分别是name、age、sex、id,他们定义的方式和定义变量与数组的方式相同,只是在定义时不能初始化。


结构体也是数据类型的一种,被称为构造数据类型,我们可以根据需求自行定义。

struct Stu s是定义结构体变量的语句:

  • 变量名是s

  • 变量类型是Stu

  • 它由4个成员组成

结构体成员的初始化和数组的初始化类似,用大括号{}括起来,根据每个成员的类型一一初始化,可以一起初始化,也可以逐个访问进行初始化。

struct Stu s={"张三",21,"男","2022211177"};
struct Stu s={
    .name="张三",
    .age=21,
    .sex="男",
    .id="2022211177"
};

无论哪种方式,在初始化时都要保证数据类型一一对应,否则编译器可能会报错。


我们定义了结构体该如何访问呢?

  • 在数组里我们使用下标[]来访问每个元素,在结构体里我们使用操作符.来访问每个成员变量
  • 除此之外,也可以通过指针来访问结构体的成员变量,有两种方式:
    1. (*ps).name,先对指针解引用,再访问成员变量,.的优先级高于*,因此必须加括号
    2. 或者使用操作符->,它指向成员变量,如ps->name


0x09 操作符

除了上文提到的操作符,C语言中还有许多其他的操作符,这里对我们做一个简单的总结和补充,后续会对操作符做一个完整详细的讲解。

操作符分为以下几类:

  • 算数运算符
算数运算符描述
+把两个操作数相加
-左操作数减去右操作数
*把两个操作数相乘
/左操作数除去右操作数
%取模运算符,整除后取余
++自增运算符,整数值增加1
--自减运算符,整数值减少1
  • 关系运算符
关系运算符描述
==检查两个操作数的值是否相等,相等则为真
!=检查两个操作数的值是否相等,不相等则为真
>检查左操作数的值是否大于右操作数,是则为真
<检查左操作数的值是否小于右操作数,是则为真
>=检查左操作数的值是否大于或等于右操作数,是则为真
<=检查左操作数的值是否小于或等于右操作数,是则为真
  • 逻辑运算符
逻辑运算符描述
&&逻辑与,两个操作数非零为真
||逻辑或,两个操作数任意一个非零为真
!逻辑非,逆转操作数的逻辑状态,如果条件为真会被置为假
  • 位运算符:&、|、^及其真值表如下:
左操作数p右操作数q与(p&q)或(p|q)异或(p^q)
00001
01010
10010
11111

​ 除此之外,还有按位取反的运算符"~":按二进制位取反,如0001取反得到1110

  • 移位运算符
移位运算符描述
<<二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)
>>二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
  • 赋值运算符
赋值运算符
=直接赋值,将右操作数的值赋给左操作数
+=加且赋值,将右操作数加上左操作数后赋给左操作数
-=减且赋值,将左操作数减去右操作数后赋给左操作数
*=乘且赋值,将右操作数乘以左操作数后赋给左操作数
/=除且赋值,将左操作数除以右操作数后赋给左操作数
%=取模且赋值,将右操作数加上左操作数后赋给左操作数
&=按位与且赋值,将右操作数加上左操作数后赋给左操作数
|=按位或且赋值,将右操作数加上左操作数后赋给左操作数
^=按位异或且赋值,将右操作数加上左操作数后赋给左操作数
<<=左移且赋值,将操作数的二进制位全部左移若干位后赋给该操作数
>>=右移且赋值,将操作数的二进制位全部右移若干位后赋给该操作数
  • 其他操作符
其他操作符描述
sizeof()()可以是具体的变量,也可以是类型,返回其大小
&取地址操作符,返回变量的地址
*解引用操作符,指向一个变量,如p=&a,*p<==>a,*p指向变量a,等价于a
exp1?exp2:exp3条件运算符,exp1为真,则执行exp2,为假则执行exp3
exp1,exp2,exp3,…expN逗号表达式,结果取最后一个expN
[]下标引用操作符,用下标来访问数组中的元素
()函数调用操作符
.结构体成员访问操作符,通过结构体名.成员变量名的方式去访问结构体成员
->结构体成员访问操作符,通过指针的方式访问结构体成员

补充一个比较重要也比较特殊的操作符,强制类型转换:(类型),强制类型转换就是把变量从一种类型转换为另一种数据类型

#include

int main()
{
    int a=20;
    int b=3;
    float ret=0;
    ret=a/b;
    printf("ret = %fn",ret);    
    ret=(float)a/b;
    printf("ret = %fn",ret);
    
    return 0;   
}

程序执行结果如下:
ret = 6.000000
ret = 6.666667

a和b都是int类型,a/b的运算结果应该也是int类型,会舍弃掉小数部分,因此为了保留小数部分,我们将运算结果强制类型转换为浮点型。



0x0A 关键字

C语言中提供了丰富的关键字,这些关键字是C语言本身预先设定好的,不能作为常量名、变量名或其他标识符名称,这是它语法规则的一部分,用户是不能自己去创造关键字的。

下面罗列了C语言中的关键字,这里先介绍一下typedef和static,其他关键字后续的学习中遇到了再深入探究。

auto	break	case	char	const	continue	default		do	double 	else enum
extern	float 	for 	goto 	if 		int long 	register 	return 	short signed
sizeof 	static 	struct 	switch 	typedef unsigned 	unoin 	void volatile 	while
1、typedef

type define的意思是类型定义,顾名思义,我们可以理解为类型重命名,这是C语言允许的。

struct Student
{
    char name[20];
    int age;
    char sex[4];
    char id[20];
};
typedef struct Student Stu;
typedef char CHARACTER;

int main()
{
    char a;
    CHARACTER b;
    struct Student stu1={"张三",21,"男","20221177"};
    Stu stu2={"李四",22,"男","20221688"};
    printf("name = %s age = %d sex = %s id = %sn",stu1.name,stu1.age,stu1.sex,stu1.id);
    printf("name = %s age = %d sex = %s id = %sn",stu2.name,stu2.age,stu2.sex,stu2.id);
    
    return 0;
}

程序执行结果这里就不再展示,感兴趣的小伙伴可以自行尝试。

  • char a;和CHARACTER b;都是定义了一个字符变量,在类型重命名后,char和CHARACTER 是等价的;

  • 用我们构造的结构体Student去定义结构体变量时,需要使用语句struct Student stu1={};,struct看起来就是多余的,但是如果不写编译器又会报错,所以我们使用typedef将结构体重命名为Stu。

  • 这样一来,代码不仅会变得简练,也更加容易理解,后续进行编程实践时,我们还会用到。


2、static

在这之前,我们介绍全局变量和局部变量时,有提到过作用域和生命周期的概念;

而在C语言程序中,如果我们使用static修饰变量或函数,能够改变这两个性质:

  • 能够将被修饰对象的作用域限制在当前文件中,而在其他文件中无效

  • 这样做可以有效避免变量命名冲突,或者在其他文件中屏蔽某个函数

static修饰全局变量:

放两段代码:

//代码1
//code_a.c
int num=100;

//code_b.c
#include

int main()
{
    printf("%dn",num);    
    return 0;
}
//代码2
//code_a.c
static int num=100;

//code_b.c
#include

int main()
{
    printf("%dn",num);   
    return 0;
}

分别执行代码1和代码2,我们会发现代码1可以正常运行,而代码2会在编译时出现链接错误。

  • 全局变量num在被static修饰后,被限制在code_a.c的源文件中,无法在其他源文件中使用;

  • 但是我们可以在其他文件中重新定义一个名为num的变量并赋值,也能够正常运行,编译器不会报变量重复定义的错误。

//代码2
//code_a.c
static int num=100;

//code_b.c
#include

int main()
{
    int num=180;
    printf("%dn",num);   
    return 0;
}

static修饰局部变量:

除了可以修饰全局变量,它还可以修饰局部变量,上代码:

//代码1
#include

void test()
{
    int a=1;
    a++;
    printf("%d ",a);
}

int main()
{
    int i=0;
    int s=0;
    for(i=0;i<5;i++)
    {
		test();    
    }
    
    return 0;
}

程序执行结果如下:

2 2 2 2 2

主函数中,使用了for循环多次调用test函数;

第一次调用test函数时,创建局部变量a并赋值为1;

在执行完a++自增语句后,将值打印输出,而后局部变量a销毁;

因此程序重复创建与销毁局部变量a,循环了五次,每次的打印结果都是2。


//代码2
#include

void test()
{
    static int a=1;
    a++;
    printf("%d ",a);
}

int main()
{
    int i=0;
    for(i=0;i<5;i++)
    {
		test();    
    }
    
    return 0;
}

程序执行结果如下:

2 3 4 5 6

与代码1不同,在使用了static修饰局部变量a后,局部变量a并没有随着函数调用结束被销毁,而是保存了下来,那么可以确定的一点就是static改变了局部变量的生命周期!

这里就不得不提到内存,在计算机中,内存被分为了三个区,栈区、堆区和静态区分别用来处理不同的数据:

  • 栈区:用来存放局部变量和函数参数等

  • 堆区:是计算机进行动态内存分配时使用的内存空间

  • 静态区:用来存放全局变量和被static修饰的静态变量

那么局部变量在被static修饰后,被放在了静态区,不会随着函数的调用结束而销毁,本质上是改变了局部变量在内存中的存储方式。


static修饰函数

//代码1
//add.c
int Add(int x,int y)
{
    return x+y;
}

//main.c
#include

int main()
{
    printf("%dn",Add(1,2));
    return 0;
}
//代码2
//add.c
static int Add(int x,int y)
{
    return x+y;
}

//main.c
#include

int main()
{
    printf("%dn",Add(1,2));
    return 0;
}

分别执行代码1和代码2,我们会发现代码1可以正常运行,而代码2会在编译时出现链接错误。

  • 和全局变量一样,一个函数被static修饰后,这个函数被限制在add.c的源文件内,不能在其他文件中使用。


本文完,如有错漏,敬请指正。笔者会继续整理C语言学习笔记,希望能和大家一起学习,共同进步,加油!!!
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/997083.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号