文章目录
一、数据类型
数据类型概念
一些数据类型表示数字,一些数据类型表示字母(字符)C通过识别一些基本的数据类型来区分和使用这些不同的数据类型。
数据类型关键字
int long short unsigned char float double
signed void
_Bool _Comlplex _Imaginary
数据类型分类
A.有符号整型
可用于表示正整数和负整数
- int(基本整数类型)C语言规定不小于16位
- short或short int(最大的short类型整数小于或等于最大的int类型整数)C语言规定至少占16位
- long或long int(该类型表示的整数大于或等于最大的int类型整数)C语言规定至少占32位
- long long或long long int(该类型可表示的整数大于或等于最大的long类型指数)long long类型至少占64位
B.无符号整型
无符号整型只能用于表示0和正整数,无符号整型可表示的正整数比有符号整型的大
- unsigned int
- unsigned long
- unsigned short
- 单独的unsigned相当于unsigned int
C.字符类型
可打印出来的符号都是字符
- char字符类型的关键字(分有符号和无符号)
可加入signed和unsigned进行区分
D.布尔类型
布尔值表示true和false分别是1和0
- -Bool—无符号int类型所占用空间只要能存储0或1即可
E.实浮点类型
可表示为正浮点数和负浮点数
- float—基本浮点类型,可精确表示至少6位有效数字
- double—能表示至少10位或更多的有效数字和更大的指数
- long double—能表示比double更多的有效数字和更大的指数
F.复数和虚数浮点数
虚数类型是可选的类型
- float-Complex
- double-Complex
- long double-Complex
- float-Imaginary
- double-Imaginary
- long double-Imaginary
详解特殊数据类型
1.整数和浮点数
通俗的讲是指书写方式不同,而对计算机而言他们的区别是储存方式不同
实际区别
(1)整数没有小数部分,浮点数有小数部分
(2)浮点数可以表示的范围比整数大
(3)一些算数运算中浮点数损失的精度更多
(4)计算机的浮点数不能表示区间内所有的值,浮点数通常只是实际值的近似值。
(5)过去浮点数运算比整数慢,现在许多CPU都包含浮点处理器,缩小了速度差距
2.int类型
int类型—有符号整型(即对应值必须是正整数,负整数,0)
有效声明:int erns;int hogs;cows;goats;
(创造变量)以上声明即为创造
(提供值)1.赋值:例如:cows = 100;
2.通过函数:例如:scanf()
3.char类型
char类型—用于存储字符
4.-Bool类型
本质为一种整数类型,且只占一位存储空间
5.可移植类型:stdint.h 和 inttypes.h
用来确保C语言的类型在各系统中的功能相同
二、运算符,表达式和语句
概念
C语言有许多运算符,如赋值运算符和算术运算符。一般而言,运算符需要一个或多个运算对象才能完成运算生成一个值。只需要一个运算对象的运算符(如负号和sizeof)称为一元运算符,
需要两个运算对象的运算符称为二元运算符
表达式由运算符和运算对象组成。
大多数语句都以分号结尾,最常用的语句是表达式语句。用花括号括起来的一条或多条语句被称为复合语句(或称为块)
实例
1.运算符
算术运算符
1.加法运算符:+
用于加法运算,相加的值可以是变量也可以是常量。如
printf("%d",4+20); income = salary + bribes
2.减法运算符:-
用于减法运算,使其左侧的值减去右侧的值
3.乘法运算符:*
用于乘法运算,不同于数学上的’x’。
4.除法运算符:/
(1)用于除法运算,不同于数学上的‘÷’。/左侧是被除数,/右侧是除数。
(2)整数除法和浮点数除法不同。浮点数除法的结果是浮点数,整数除法的结果是整数。
#include <stdio.h>
int main()
{
printf("%d\n",5 / 4);
printf("%d\n",6 / 3);
printf("%.2f\n",7. / 4.);
printf("%.2f\n",7. / 4);
return 0;
}
5.求模运算符:%
(1)用于整数运算,不能用于浮点数,求模运算符给出其左侧整数除以右侧整数的余数。13%5(读作:13求模5)。
(2)求模运算符常用于控制程序流
(3)负数求模:如果第一个运算对象为负数,那么求模结果为负数;如果第一个运算对象为正数,那么求模结果为正数
6.符号运算符:+和-
用于标明或改变一个值的代数符号。它们是一元运算符(单目运算符),即只需要一个运算对象。
7.递增运算符:++
(1)功能:将其运算对象递增1
(2)两种形式:①前缀模式:++出现在其作用的变量前面
②后缀模式:++出现在其作用的变量后面
(3)这种缩写形式的好处:让程序更加简洁、美观,可读性更高。
(4)前缀模式:先++,再使用
后缀模式:先使用,再++
8.递减运算符:–
与++同理。
Ⅱ、赋值运算符
1.赋值运算符:=
1°、一些术语:
(1)数据对象:用于存储值的数据存储区
(2)左值:用于标识特定数据对象的名称或表达式。
Tips:提到左值,这意味着它①指定一个对象,可以引用内存中的地址②可以用在赋值运算符的左侧。但是const创建的变量不可修改,不满足②,所以后来又提出了可修改的左值这一概念,用于标识可修改的对象
(3)右值:能赋值给可修改左值的量,且本身不为左值
2°、在C语言中,=不意味着“相等”,而是一个赋值运算符,num = 2021,读作“把值2021赋给变量num”,赋值行为从右往左进行
3°、2021 = num;?在C语言中类似这样的语句是没有意义的,因为此时,2021被称为右值,只能是字符常量,不能给常量赋值,常量本身就是它的值。因此,我们要记住赋值运算符左侧必须引用一个存储位置,最简单的方法就是使用变量名。C使用可修改的左值标记那些可赋值的实体。
4°、C语言中不回避三重赋值,赋值顺序从右向左,如a = b = c = 100;首先把100赋给从c,然后再赋给b,最后赋给a。
2.其他赋值运算符:+=、-=、*=、/=、%=
2.表达式
下面是一些表达式:
4 -6 4+
21
a*(b + c/d)/20
q = 5*2
x = ++q % 3
q > 3
C 表达式的一个最重要的特性是, 每个表达式都有一个值。
表达式q = 5*2作为一个整体的值是10。 那么, 表达式q > 3的值是多少? 这种关系表达式的值不是0就是1
3.语句
- 语句是C语言程序的基本单元,一条语句就相当于一条完整的计算机指令。在C语言中,大部分语句都是以分号结尾的。
- 在编写C语言代码时,通常空格的有无以及数量是没有限制的,但需要满足没有歧义这一前提。
- C语言代码中的所有字符(除了字符串的内容为外,这个在讲到字符串时会说明),都必须是英文字符。比如中文输入法打出来的分号或双引号会由于编译器无法检测到该符号而导致程序编译失败,这是非常容易出现错误的地方。当然,在很多编译器上,中英文符号会给予不同的显示以便于我们发现错误。
三、循环
1.在C语言程序中,一共有三种程序结构:顺序结构、选择结构(分支结构)、循环结构;顺序结构,从头到尾一句接着一句的执行下来,直到执行完最后一句;选择结构,到某个节点后,会根据一次判断的结果来决定之后向哪一个分支方向执行; 循环结构,循环结构有一个循环体,循环体里是一段代码。对于循环结构来说,关键在于根据判断的结果,来决定循环体执行多少次。C语言循环控制语句是一个基于C语言的编程语句,该语句主要有while循环语句、do-while循环语句和for循环语句来实现循环结构。
2.注意
- 注意循环的测试条件是要能使循环结束
- 确保循环测试中的值在首次使用之前要确保初始化
- 确保循环在每次迭代都要更新测试的值
3.主要循环语句比较
同一个问题,往往既可以用 while语句解决,也可以用 do-while或者for语句来解决,但在实际应用中,应根据具体情况来选用不同的循环语句。选用的一般原则是:
(1) 如果循环次数在执行循环体之前就已确定,一般用 for语句。如果循环次数是由循环体的执行情况确定的,一般用 while语句或者do- while语句。
(2) 当循环体至少执行一次时,用 do-while语句,反之,如果循环体可能一次也不执行,则选用while语句。
C++/C循环语句中,for语句使用频率最高,while语句其次,do语句很少用。
三种循环语句for、while、do-while可以互相嵌套自由组合。但要注意的是,各循环必须完整,相互之间绝不允许交叉。
四、分支和跳转
1.if语句
#include<stdio.h>
int main(void)
{
const int FREEZING = 0;
float temperature;
int cold_days = 0;
int all_days = 0;
printf("Enter the list of daily low temperatures.\n");
printf("Use Celsius,and enter q to quit.\n");
while (scanf_s("%f", &temperature)) {
all_days++;
if (temperature < FREEZING)
cold_days++;
}
if (all_days != 0)
printf("%d days total:%.lf%% were below freezing.\n", all_days,
100.0*(float)cold_days / all_days);
if (all_days == 0)
printf("No data entered!\n");
system("pause");
return 0;
}
/*
if (expression) 分支语句。如果expression为真,则执行statement
statement
*/
2.getchar()和putchar()
两者只处理字符
#include<stdio.h>
#define SPACE ' '
int main(void)
{
char ch;
ch = getchar();
/*该函数不带任何其他参数,它从输入队列中返回一个字符*/
while (ch != '\n')
{
if (ch == SPACE)
putchar(ch); //打印字符
else
putchar(ch + 1);
ch = getchar();
}
putchar(ch);
system("pause");
return 0;
}
3.多重选择else if
#include<stdio.h>
#define RATE1 0.13230
#define RATE2 0.15040
#define RATE3 0.30025
#define RATE4 0.34025
#define BREAK1 360.0
#define BREAK2 468.0
#define BREAK3 720.0
#define BASE1 (RATE1*BREAK1)
#define BASE2 (BASE1+(RATE2*(BREAK2-BREAK1)))
#define BASE3 (BASE1+BASE2+(RATE3*(BREAK3-BREAK2)))
int main(void)
{
double kwh;
double bill;
printf("Please enter the kwh used.\n");
scanf_s("%lf", &kwh);
if (kwh <= BREAK1)
bill = RATE1 * kwh;
else if (kwh <= BREAK2)
bill = BASE1 + (RATE2*(kwh - BREAK1));
else if (kwh <= BREAK3)
bill = BASE2 + (RATE3*(kwh - BREAK2));
else
bill = BASE3 + (RATE4*(kwh - BREAK3));
printf("The charge for %.1f kwh is $%1.2f.\n", kwh, bill);
system("pause");
return 0;
}
4.ctype.h头文件
五、函数
1.定义
在 C 语言中,函数由一个函数头和一个函数主体组成。下面列出一个函数的所有组成部分:
返回类型:一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。
函数名称:这是函数的实际名称。函数名和参数列表一起构成了函数签名。
参数:参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
函数主体:函数主体包含一组定义函数执行任务的语句。
2.注意
1.函数类型是指返回值的类型,即要与return语句后跟的表达式的值的类型一致。若函数类型为void则说明该函数无返回值,即函数体里可以不出现return语句
2.形式参数列表里定义的变量要记得给它们指定类型,而且如果同时要定义多个,应在每个前面都分别指定类型名,而不能写成 int x,y;
3.函数体里能写的语句跟main函数一样,在开头可定义所需要的变量,后面跟上一堆执行语句。
六、数组
数组是在内存中连续存储的具有相同类型的一组数据的集合。
一维数组
一维数组的定义方式如下:
类型说明符 数组名[常量表达式];
它表示定义了一个整型数组,数组名为 a,定义的数组称为数组 a。数组名 a 除了表示该数组之外,还表示该数组的首地址(关于地址现在先不讨论,稍后讲指针的时候再说)。
此时数组 a 中有 5 个元素,每个元素都是 int 型变量,而且它们在内存中的地址是连续分配的。也就是说,int 型变量占 4 字节的内存空间,那么 5 个int型变量就占 20 字节的内存空间,而且它们的地址是连续分配的。
这里的元素就是变量的意思,数组中习惯上称为元素。
在定义数组时,需要指定数组中元素的个数。方括号中的常量表达式就是用来指定元素的个数。数组中元素的个数又称数组的长度。
数组中既然有多个元素,那么如何区分这些元素呢?方法是通过给每个元素进行编号。数组元素的编号又叫下标。
数组中的下标是从 0 开始的(而不是 1)。那么,如何通过下标表示每个数组元素的呢?通过“数组名[下标]”的方式。例如“int a[5];”表示定义了有 5 个元素的数组 a,这 5 个元素分别为 a[0]、a[1]、a[2]、a[3]、a[4]。其中 a[0]、a[1]、a[2]、a[3]、a[4] 分别表示这 5 个元素的变量名。
为什么下标是从 0 开始而不是从 1 开始呢?试想,如果从 1 开始,那么数组的第 5 个元素就是 a[5],而定义数组时是 int a[5],两个都是 a[5] 就容易产生混淆。而下标从 0 开始就不存在这个问题了!所以定义一个数组 a[n],那么这个数组中元素最大的下标是 n–1;而元素 a[i] 表示数组 a 中第 i+1 个元素。
另外,方括号中的常量表达式可以是“数字常量表达式”,也可以是“符号常量表达式”。但不管是什么表达式,必须是常量,绝对不能是变量。通常情况下 C 语言不允许对数组的长度进行动态定义,换句话说,数组的大小不依赖程序运行过程中变量的值。非通常的情况为动态内存分配,此种情况下数组的长度就可以动态定义
七、结构体,联合体
1.结构体:
1.在C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体的定义形式为:struct 结构体名{ 结构体所包含的变量或数组};
2.结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员。
3.结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据:像 int、float、char 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型;而结构体可以包含多个基本类型的数据,也可以包含其他的结构体,我们将它称为复杂数据类型或构造数据类型。
4.结构体变量:既然结构体是一种数据类型,那么就可以用它来定义变量。例如:struct stu stu1, stu2;定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。
5.结构体和数组类似,也是一组数据的集合,整体使用没有太大的意义。数组使用下标[ ]获取单个元素,结构体使用点号.获取单个成员。
6.注意:结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要内存空间来存储。
2.联合体:
1.像结构体一样,联合体(Union)在C语言中是一个用户定义的数据类型,用于保存不同类型的元素,但它并不占所有成员的内存总和。它只占最大成员的内存,它分享最大成员的内存。
2.联合体完全就是共用一个内存首地址,并且各种变量名都可以同时使用,操作也是共同生效。如此多的访问内存手段,确实好用,不过这些“手段”之间却没法互相屏蔽——就好像数组+下标和指针+偏移一样。
3.由于联合体中的所有成员是共享一段内存的,因此每个成员的存放首地址相对于于联合体变量的基地址的偏移量为0,即所有成员的首地址都是一样的。为了使得所有成员能够共享一段内存,因此该空间必须足够容纳这些成员中最宽的成员。对于这句“对齐方式要适合其中所有的成员”是指其必须符合所有成员的自身对齐方式。
4.联合体优点:
它占用较少的内存,因为它只占最大的成员的内存量。
5.联合体缺点:
它将数据存储在一个成员中。
八、指针
指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。
请看下面的实例,它将输出定义的变量地址:
# include <stdio.h>
int main(void)
{
int a[5];
a[5] = {1, 2, 3, 4, 5};
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
var_runoob 变量的地址: 0x7ffeeaae08d8
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:
#include <stdio.h>
int main ()
{
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */
ip = &var; /* 在指针变量中存储 var 的地址 */
printf("var 变量的地址: %p\n", &var );
/* 在指针变量中存储的地址 */
printf("ip 变量存储的地址: %p\n", ip );
/* 使用指针访问值 */
printf("*ip 变量的值: %d\n", *ip );
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
var 变量的地址: 0x7ffeeef168d8
ip 变量存储的地址: 0x7ffeeef168d8
*ip 变量的值: 20
九、宏定义
优点
方便程序的修改
使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时, 我们可以用较短的有意义的标识符来写程序,这样更方便一些。
相对于全局变量两者的区别如下:
- 宏定义在编译期间即会使用并替换,而全局变量要到运行时才可以。
- 宏定义的只是一段字符,在编译的时候被替换到引用的位置。在运行中是没有宏定义的概念的。而变量在运行时要为其分配内存。
- 宏定义不可以被赋值,即其值一旦定义不可修改,而变量在运行过程中可以被修改。
- 宏定义只有在定义所在文件,或引用所在文件的其它文件中使用。 而全局变量可以在工程所有文件中使用,只要再使用前加一个声明就可以了。换句话说,宏定义不需要extern。
提高程序的运行效率
使用带参数的宏定义可完成函数调用的功能,又能减少系统开销,提高运行效率。正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生==函数调用 ==时,需要保留调用函数的现场,以便子 函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽 略,但如果子函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了,但使用带参数的宏定义就不会出现这个问 题,因为它是在预处理阶段即进行了宏展开,在执行时不需要转换,即在当地执行。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义所占用的目标代码空间相对较大。所以在使用时要依据具体情况来决定是否使用宏定义。
总结
历时多天,结合了一些资料,总算完成了C语言知识的第一次总结。继续努力吧,希望接下来几期会越来越熟练的完成,之后会再来补充一些新知识,也会做好记录,就当回忆了。