当前位置: 首页 > news >正文

【C语言基础】Chap. 2. 基本概念

1. 数据类型

计算机中运行的程序是为了解决现实世界中的问题,因此撰写程序的代码也必须拥有描述现实事物的能力,比如数字、文字,又比如人的年龄、商品的价格等。

为了实现这种能力,C语言拥有一套类型系统:

  • char:字符数据类型。
  • short:短整型。
  • int:整型,即integer,用于存储整数(包括正负)。
  • long:长整型。
  • long long:更长的整型。
  • float:单精度浮点数,即float-point,可以存储较大的数值,并且可以带小数位。
  • double:双精度浮点数。

:float(和double)进行算术运算时会比int类型慢。且float类型的变量存储的数值往往只是实际数值的一个近似值,如存进0.1,会得到0.099 999 999 999 999 87,这是舍入造成的误差。float类型的数值通常分为两部分存储:小数部分(或称尾数部分)和指数部分。如12.0就可以以1.5×2^3的形式存储,其中1.5是小数部分,而3是指数部分。有些编程语言将其称为real类型。

并且每种类型都在存储的空间中占有一定的大小,我们可以用sizeof这个操作符来计算类型变量所占空间的大小。


 

printf("%d\n", sizeof(char)); // 1
printf("%d\n", sizeof(short)); // 2
printf("%d\n", sizeof(int)); // 4
printf("%d\n", sizeof(long)); // 4 C语言标准:sizeof(long) >= sizeof(int)
printf("%d\n", sizeof(long long)); // 8
printf("%d\n", sizeof(float)); // 4
printf("%d\n", sizeof(double)); // 8

注意,char类型指的是如a、b这样的字符;而不是Hello world这样的字符串

C语言有没有字符串类型呢?答案是没有,但实际的使用中是存在的,因为可以用一连串的字符来组成一个字符串,可以将其理解为字符的数组

字符一般用单引号''括起来,而字符串则用双引号""括起来。

注1:上面说的类型的大小是以字节为单位的。

注2:在计算机存储中有如下单位及其换算:

  • bit - 比特位
  • byte - 字节 == 8 bit
  • kb - 千字节 == 1024 byte
  • mb - 兆字节 == 1024 kb
  • gb - 千兆字节 == 1024 mb
  • tb - 太字节 == 1024 gb
  • pb - 拍字节 == 1024tb

2. 变量和常量

刚刚提到sizeof可以计算变量的大小,变量是个什么样的概念呢?

在数学题中,我们经常会见到未知数x,在不同的题目x可以被直接赋值,也可以被计算出来再赋值。变量与之含义类似。

我们再思考一下,在现实世界中,有许多数据是不可变的,比如一个人的血型,比如圆周率。但与此同时,有些数据却是不断改变的,比如一个人从出生后不断增长的年龄,又比如一座工厂每年的产量。

在C语言中也有描述这样的变与不变的数据的概念,即变量常量,它们的功能便是存储数据。

变量和常量同样有自己的数据类型,类型会影响变量的存储方式以及允许对变量进行的操作。

1.变量的分类

C语言中有两种变量,分别是全局变量局部变量

简单理解的话,全局变量是在{}外部定义的,整个工程都能够使用它;而局部变量是在{}内部定义的,只能在{}内部使用——但实际上只能在单独的.c文件里使用的静态变量也算是局部变量的一种。

当全局变量与局部变量的名字冲突时,局部优先,但一般不建议把全局变量和局部变量的名字设成相同的。

使用变量前需要先进行声明(为编译器所做的描述),一般流程是指定变量类型,陈述变量名(也称标识符identifier,变量名由程序员自定义)。


 

//写一个代码求2个整数的和
int main()
{
    int a = 0;
    int b = 0;
    int sum = 0;
    scanf("%d %d", &a, &b); //scanf函数是与printf对应的输入函数,f同样是format的意思
    //双引号中的格式串用于指定输入的格式
    //&运算符是用于读取其后变量的地址的,后续会做更多解释
    sum = a + b;
    printf("sum = %d\n", sum); return 0;
}

注1:当报错'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.时,在文件第一行写上#define _CRT_SECURE_NO_WARNINGS 1便可。

注2:scanf_s函数 是由VS编译器提供的,并非C语言标准规定的,因此学习时不建议使用。

{}中的第1至第3行的意义是相似的,都是声明变量并赋予初始值,开头的int说明它们是int类型的变量, a、b、sum 则是它们的名字,=赋值(assignment)的意思,=后的内容则是赋给它们的值,是一些int类型的字面常量(后面会解释)。

比如第一行int a = 0;的意思就是声明一个整型变量a,并赋初始值0。

实际上,,当几个变量具有相同的类型时,可以将其声明合并:


 

int a = 0, b = 0, sum = 0;

注1:在C语言在内的许多编程语言中,=的意思是赋值,==的意思才是相等。

注2:变量的声明往往在其被使用之前(C99标准不然,但本文主要陈述C89标准)。

注3:每条完整的语句(不止是声明语句)都要以分号结尾。

当然,声明变量的时候也可以不赋值:

int a, b, sum;

 

但这就失去了主动给变量初始化(initialize,常用缩写init)的机会,在未初始化的(uninitialized)变量被赋值之前,访问它可能会导致某些错误,甚至程序崩溃。

所以在声明的时候便进行赋值,是良好的习惯。

注1:初始化时=右边的值或公式被称为初始化式(initializer)。

注2:赋值可以在声明之后,但不能在声明之前。

赋值运算的右侧可以是一个含有常量、变量和运算符的公式(C语言中称为表达式):

int a = 1, b = 1
int c = a + b;

同样,printf函数中的赋值部分也可以使用如下的形式:

printf("sum = %d\n", a + b);

 

这是C语言的一个通用原则:在任何需要数值的地方,都可以使用具有相同类型的表达式

另外,当将包含小数点的常量赋予float类型的变量时,最好在该常量后面加上一个字母f(即float):

float num;
num = 2150.48f;

 

否则编译器可能会产生警告,因为某些编译器默认带小数的数字为double类型,直接使用可能丢失精度。

值得注意的是,将某个类型的值赋给另一个类型的变量,不一定会报错,但可能会出错。

2. 变量的作用域和生命周期

作用域(scope),是一个程序设计概念,通常而言,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。其中:

  • 局部变量的作用域是变量所在的局部范围。
  • 全局变量的作用域是整个工程。
//在其他文件定义的全局变量,本文件使用前需要用关键字extern声明一下变量
extern int g_val;
int main()
{
    printf("%d\n", g_val);
    return 0;
}

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

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

3. 常量

C语言中的常量与变量的定义形式有所差异,前者分为以下几种:

  • 字面常量(literal constant):由其字面意义就可以得知其值和类型,如3.14、10、'a'、'abcdef'。
  • const 修释的常变量
    • 既是变量,又具有常属性(不能被改变),如const int n = 10初始化后,就不能对n重新赋值了;
    • 有时不能作为普通常量使用,如int arr2[n] = { 0 },声明数组时[]里的内容必须是常量,但常变量不行。
  • #define定义的标识符常量(identifier constant):使用宏定义的方式定义的常量,如#define MAX 10000,在大括号内外都可定义。
  • 枚举常量:可以一一列举的常量,如性别、星期一至星期日,如下:
enum Sex //enum是枚举关键字
{
    //枚举常量是常量,其值默认是从0开始,但也可以赋值
    MALE,
    FEMALE = 3, //赋初值
    SECRET
};
int main()
{
    enum Sex s = FEMALE; 
    MALE = 3; //枚举常量不能在其他地方赋值,此行报错
    printf("%d\n", MALE); //0
    printf("%d\n", FEMALE); //3
    printf("%d\n", SECRET); //4
    printf("%d\n", s); //虽然被赋值为3,但显示是1;
}

3. 字符串 + 转义字符 + 注释

1. 字符串

类似"Hello World.\n"这样由双引号引起来的一连串字符,称为字符串字面量/值(string literal),或简称字符串(string)。

:字符串的结束标志是一个'\0'的转义字符。在计算字符串长度的时候'\0'是结束标志,不算作字符串内容。

下面是关于字符串的打印和长度计算,可以先看看,完全看不懂就先跳过

 


 

//包含头文件,这些文件里有我们需要的函数或宏等
#include <stdio.h> //库名为 standard input and output 的缩写,意为“标准的输入和输出”
#include <string.h>

int main()
{
    //字符串可以视为字符数组 - 数组指的是一组相同类型的元素
    //字符串字面量在结尾的位置隐藏了一个 \0 的字符
    char arr[] = "Hello";
    char arr1[] = "abc";
    char arr2[] = {'a', 'b', 'c'};
    char arr3[] = {'a', 'b', 'c', '\0'};
    
    //打印字符串
    printf("%s\n", arr1); //abc
    printf("%s\n", arr2); //abc烫烫烫烫蘟bc
    //乱码的原因是arr2没有 \0 结束标志,如果加上就会变得正常
    printf("%s\n", arr3); //abc
    
    //求字符串长度
    int len = strlen("abc"); //strlen是String Length的缩写
    printf("%d\n", len); //3
    printf("%d\n", strlen(arr1)); //3
    printf("%d\n", strlen(arr2)); //随机值
    //此行报错:C6054:可能没有为字符串"arr2"添加字符串零终止符
    printf("%d\n", strlen(arr3)); //3 return 0;
}

2. 转义字符

转义字符(或称字符转义序列)即转变了某个字符原来的意义,是字符串包括该特殊字符而不会使编译器引发问题。

C语言中的转义字符如下:

转义字符释义
\?书写连续多个问号时使用,防止被解析成三字母词(非所有编译器都会出现)。
\'用于表示字符常量。
\"用于表示一个字符串内部的双引号。
\\用于表示一个反斜杠,防止它被解释为一个转义序列符。
\a警告字符,蜂鸣。
\b退格符:类似Backspace键,也是一个字符,但显示时是将光标退回前一个字符,而不会删除光标位置的字符;如果后边有新的字符,将覆盖退回的那个字符,这与在文本编器中按Backspace的效果不一样。
\f进纸符。
\n换行。
\r回车。
\t水平制表符:按8个字符宽度跳到下一个制表位置,但本身算1个字符。
\v垂直制表符。
\ddd表示1~3个八进制的数字,如:\130 X。
\xdd表示2个十六进制的数字,如:\x30 0。

注:在ASCII码表(又叫ASCII字符集)中,每一个字符都有一个值——ASCII码值。ASCII(美国信息交换标准码)码表用7位代码表示128个字符。

3. 注释

  1. 代码中不需要的代码可以直接删除,也可以注释掉。
  2. 代码中有些代码比较难以理解,则可以加上注释文字进行解释。
  3. 预处理(预编译)阶段注释就会被删除,不会影响程序功能。


 

int main()
{
    //int a = 10; //C++注释风格,允许嵌套 
    /* 
    * int b = 0; //C语言注释风格,不允许嵌套
    */
    return 0;
}

4. 选择语句

选择语句可能是最常见的流程控制语句,能够根据表达的值执行多条语句中的一条。

示例:

int input = 0; //输入的值
printf("现在是白天还是晚上(1/0)?>:");
scanf("%d", &input);
if (input == 1) 
{
    printf("白天\n");
}
else
{
    printf("晚上\n");
}

 当内容只有一句的时候,选择语句的大括号可加可不加:

if (input == 1) 
    printf("白天\n");
else
    printf("晚上\n");

 

5. 循环语句

在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句,此时就可以用到循环语句(loop statement)。

循环语句的结构中包括循环变量循环体循环终止条件

示例:

//while语句,for语句和do...while语句后面讲
int line = 0;
//以下开始循环,使line增加到50000
while (line < 50000)
{
    printf("写代码:%d\n", line);
    line++;
}
if (line ==  50000)
    printf("line == %d\n", line); //line == 50000

6. 函数

1. 传统定义

一般的,在一个变化过程中,假设有两个变量x、y,如果对于任意一个x都有唯一确定的一个y和它对应,那么就称x是自变量,y是x的函数。x的取值范围叫做这个函数的定义域,相应y的取值范围叫做函数的值域。

2. 近代定义

设A,B是非空的数集,如果按照某种确定的对应关系f,使对于集合A中的任意一个数x,在集合B中都有唯一确定的数 和它对应,那么就称映射 为从集合A到集合B的一个函数,记作 y=f(x),x∈Ay=f(x), x∈Ay=f(x),x∈A 或 f(A)=y∣f(x)=y,y∈Bf(A)={y|f(x)=y,y∈B}f(A)=y∣f(x)=y,y∈B 。

其中x叫作自变量,yyy 叫做x的函数,集合 AAA 叫做函数的定义域,与x对应的y叫做函数值,函数值的集合 f(x)∣x∈A{f(x)|x∈A}f(x)∣x∈A 叫做函数的值域, 叫做对应法则。

其中,定义域、值域和对应法则被称为函数三要素。一般书写为 y=f(x),x∈Dy = f(x),x∈Dy=f(x),x∈D 。若省略定义域,一般是指使函数有意义的集合。

3. 编程

函数过程中的这些语句用于完成某些有意义的工作——通常是处理文本控制输入计算数值。通过在程序代码中引入函数名称和所需的参数,可在该程序中执行(或称调用)该函数。

类似过程,不过函数一般都有一个返回值。它们都可在自己结构里面调用自己,称为递归

大多数编程语言构建函数的方法里都含有函数关键字(或称保留字),但C语言没有。

编程中的函数与数学中的相似,但也有所不同。相同之处在于它们都能完成一定的任务,行使特定的功能。但编程中的函数更应该理解为一段预处理好的程序。

示例:写一个函数,用于输出用户输入的两个整数的和


 

int Add(int x, int y) //一般不建议把函数的名字写成全大写
{
    //int z = 0;
    //z = x + y;
    int z = x + y;
    return z;
}

int main()
{
    int num1 = 0;
    int num2 = 0;
    scanf("%d%d", &num1, &num2);
    //int sum = num1 + num2; //直接计算
    //函数方式解决
    int sum = Add(num1, num2);

    printf("%d\n", sum);

    return 0;
}

7. 数组

C语言中的定义:一组相同类型元素的集合。

示例:

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; //整型数组,完全初始化
char ch[5] = { 'a', 'b','c' }; //字符数组,不完全初始化,剩余的默认为0

//数组内的元素是用下标来访问的
int i = 0;
while (i < 10)
{
    printf("%d ", arr[i]);
    i++;
}

 

指定数组大小时一般使用常量,而不能用变量。除非使用C99标准中引用一个概念:变长数组 / 柔性数组(flexible array),变长数组支持数组创建的时候用变量指定大小,但这个数组不能初始化。

8. 输入和输出

C语言的输入和输出实际上有多种方式,这里先介绍scanfprintf这两个使用最频繁的函数。

1. printf

pinrtf函数被设计用来显示格式串(format string)的内容,并在该串中的指定位置插入可能的值。其使用格式为:


 

printf(格式串, 表达式1, 表达式2, ...);

显示的值可以是常量、变量或者更加复杂的表达式。调用printf函数一次可以打印的值的个数没有限制。

格式串包含普通字符转换说明(conversion sprcification),其中转换说明以字符%开头,用来表示打印过程中待填充的值的占位符,%后的信息指定了把数值从内部形式(二进制)转换成打印形式(字符)的方法,即printf在打印数据的时候,可以指定打印的格式。

printf的f实际上是格式化(format)的意思,即以用户指定的格式显示到窗口上。

占位符%d指明变量的类型以及显示时位置,d是decimal(十进制格式)的意思。

双引号中的内容为格式串(format string),用于指定输出的格式,主要有以下几种:

  • %d - int
  • %f - float
  • %lf - double
  • %c - char
  • %s - string
  • %p - 地址
  • %x - 十六进制打印

当表达式多于转换说明时,多余的表达式结果不会被展示;而表达式少于转换说明时,多出来的转换说明会显示一个无意义的值,值的格式与转换说明指定的格式相同。一般尽量保证两者相等。

另外,表达式的结果会服从转换说明指定的数据类型,如果不匹配,则会强制转换——但往往也失去了其意义。

2. scanf

与用特定格式显示输出的printf函数相对,scanf根据特定的格式读取输入,它同样也包含普通字符转换说明两个部分。

但在许多情况下,scanf函数只包含转换说明:

int x = 0, y = 0;
scanf("%d%d", &x, &y);

scanf函数将读入用户输入的信息如:1 20,然后将其分别赋值给x、y。

printf函数一样,scanf函数也需要关注转换说明与输入变量的数量是否匹配,例子中的%dprintf中的是一样的,说明它会读取整型类型的输入值。%d有两个,说明会读取两个值。而&操作符则是用来取程序员想要更新的变量地址的。

可以通过设置转换说明里的内容来限定scanf读取的值,比如:


 

scanf("%1d%1d", &x, &y);

那么它只会读取两个一位数,哪怕用户连续地输入11,也会被读取为1 1

3. 补充

(1)格式串中的普通字符同样具有意义。

  • 空白字符:挡在格式串中遇到一个或多个连续的空白字符时,scanf函数从输入中重复读空白字符直到遇到一个非空白字符(把该字符“放回原处”)为止。格式串中空白字符的数量无关紧要,格式串中的一个空白字符可以与输入中任意数量的空白字符相匹配。
  • 其他字符:当在格式串中遇到非空白字符时,scanf函数将把它与下一个输入字符进行比较。如果两个字符相匹配,那么scanf函数会放弃输入字符而继续处理格式串。如果两个字符不匹配,则会把不匹配的字符放回输入中,然后异常退出,而不进一步处理格式串或从输入中读取字符。

注:格式串中的普通字符都是可选的。

(2)%i也可以用于读写整数,它与%d之间是否有区别?

答:在printf格式串中使用时没有区别,但在scanf格式串中%d只能与十进制数相匹配,而%i则可以匹配八进制、十进制或者十六进制数。如果输入的前缀有0(如012),那么%i会将其当作八进制来处理;如果前缀有0x或0X,则会被作为十六进制数来处理。

(3)如果需要在printf中打印出%,则连续写两个%,此时printf将显示出一个字符%

(4)scanf函数如何把字符“放回原处”并在以后再次读取?

用户键入时,程序并没有立刻读取,而是将用户的输入放在一个隐藏的缓冲区中,由scanf函数来读取。scanf函数把字符放回缓冲区中供后续读取是非常容易的。


 

相关文章:

  • Google protobuf使用技巧和经验总结
  • Kotlin协程:Flow基础原理
  • linux格式化输入输出
  • 一篇文章告诉你,为什么必须要学Excel?
  • Intellij IDEA--Undo Commit,Revert Commit,Drop Commit的区别
  • 三个工厂模式(通俗易懂)
  • 本地环境下启动openFaas创建的Java的云函数
  • Java 第三阶段增强分析需求,代码实现能力【正则表达式】
  • Java基础【理解版】
  • 《Mycat分布式数据库架构》之搭建详解
  • Opencv项目实战:02 角度探测器
  • OSPF —— 多区域部署 + ABR + ASBR + 路由重分发
  • 猿创征文|JVM之图解垃圾收集器2-Shenandoah和ZGC
  • springboot+安卓app电子阅览室系统毕业设计源码016514
  • 第04章 第04章 队列
  • CEF与代理
  • ES6--对象的扩展
  • ES6核心特性
  • github从入门到放弃(1)
  • golang 发送GET和POST示例
  • Javascript基础之Array数组API
  • javascript面向对象之创建对象
  • Java的Interrupt与线程中断
  • js学习笔记
  • Linux各目录及每个目录的详细介绍
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 关于springcloud Gateway中的限流
  • 回顾2016
  • 将 Measurements 和 Units 应用到物理学
  • 前端学习笔记之观察者模式
  • 如何利用MongoDB打造TOP榜小程序
  • 入门到放弃node系列之Hello Word篇
  • 使用docker-compose进行多节点部署
  • 用jQuery怎么做到前后端分离
  • 用quicker-worker.js轻松跑一个大数据遍历
  • Prometheus VS InfluxDB
  • (aiohttp-asyncio-FFmpeg-Docker-SRS)实现异步摄像头转码服务器
  • (C)一些题4
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (免费领源码)Java#Springboot#mysql农产品销售管理系统47627-计算机毕业设计项目选题推荐
  • (一)Mocha源码阅读: 项目结构及命令行启动
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)详解PHP处理密码的几种方式
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复
  • .cfg\.dat\.mak(持续补充)
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)
  • .NET CF命令行调试器MDbg入门(一)
  • .NET6 开发一个检查某些状态持续多长时间的类
  • .Net各种迷惑命名解释
  • .NET关于 跳过SSL中遇到的问题
  • @Builder用法
  • @value 静态变量_Python彻底搞懂:变量、对象、赋值、引用、拷贝
  • [ C++ ] STL---string类的模拟实现
  • [ 隧道技术 ] cpolar 工具详解之将内网端口映射到公网
  • [AutoSar]BSW_Com02 PDU详解