目录
前言
本章将介绍C语初级的余下部分。
主要包括了操作符,常见关键字,关键字中又重点介绍了static、typedef、#define 定义常量和宏、指针和结构体。
一、操作符
对于部分的操作符号不过多讲解,以后遇到在解释。
对于操作符我们也是分类的,首先介绍的第一类就是算术操作符。
和数学中的操作符很像,就是加减乘除等于。+、-、/、*(×),计算机语言中都适用。c语言中一个等号(=)并不表示等于,而是表示赋值,两个等号才能表示等于(==)。
例如:
float a=1+2;
float b=1*2;
float c=5/2;
float d=2-1;
其他都没有什么多解释的,唯一需要注意的只有除法。
看看编译器的结果
它并不是我们所想的那样结果为2.50000,而是为2.00000。这是为什么呢?
在c语言中定义“/”左右两边都为整数是计算的结果就为整数,所以结果为2.5后转换为int类型后就是2,因为结果又为float,所以为2.00000。
。如果将左右两边的int换成float结果就和我们想的一样了。
位移操作符:
<<(左移)和>>(右移),移位运算符组成的表达式也属于算术表达式,其值为算术值。左移运算是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补0,右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补0,或者补符号位,这由不同的机器而定。在使用补码作为机器数的机器中,正数的符号位为0,负数的符号位为1。
以后我们还会专题见位移操作符,不用纠结在这里。
位操作符:
&、|、^、~
& 按位与
| 按位或
^ 按位异或
~取反
这里不做讲解
! | 逻辑反操作 |
- | 负值 |
+ | 正值 |
& | 取地址 |
sizeof | 操作数的类型长度(以字节为单位) |
~ | 对一个数的二进制按位取反 |
-- | 前置、后置-- |
++ | 前置、后置++ |
* | 间接访问操作符(解引用操作符) |
(类型) | 强制类型转换 |
我们知道了==表示判断相等,由于C没有bool类型所以C使用了相等返回1表示为真,不相等返回0,表示为假。
那么如何判断为不等于呢,C提供了逻辑反操作:!
a!=b,表示判断a是否不等于b。返回类型与==一样。
- 、+ 这就和数学中的用法一样。
&取地址操作符,我在这里简单讲一下地址。我们C的内存位置分为三类,栈区,堆区,静态区。栈区主要存放一些变量等,堆区存放一些malloc等我们动态分配的内存等,而堆区主要存放static修饰的量和常量等。我们一旦创建一个量就需要给该量分配一个地址用于存放该量。我们给每个内存都赋予了一个编号,用于管理等等。这个地址不是我们主动分配的,而是由机器决定。并且我们的内存是有限的有时需要考虑内存问题。
在生活中,我们讨论一个计算机是32位,64位机器,那这些32位,64位是什么意思。32位机器有32根地址线,地址线是物理线,它可以通电,通电的时候它的正电就是1,负电就为0。有正负电之分就产生了1/0这样的信号。我们把电信号转换为数字信号,这个时候就组成了由1和0组成的二进制序列。那32根地址线产生的二进制序列就有32个全0到32个全1,2^32个二进制序列。当一个这样的二进制序列成为一个内存编号的时候,我们就把这个编号称为这个内存的单元的地址。
而这个&就是用于访问地址的。
siziof之前有提到过,这就不再继续提了。
-- 、++又叫做自增、自减运算符。
举例说明:
a++就是说a=a+1;++a也是如此。但是二者还是有区别的。
a++;//后置++表示,先对a进行预算,在自增一次
++a;//先对a自增一次,以后再进行运算
不要随意使用自增自减运算符,这里会出现很多错误。比如b+++a,计算机不能识别你是b+ 一个自增以后的a还是要b自增+a呢。还有就是(a++)+(a++)+(a++)像这种代码会有很大问题的,这个代码在不同的编译器下造成的结果是不一样的。所以这种代码千万不要写出来。
自减运算同理。
(类型)表示强制类型转换 例:
float num=2;
int b=(int)a;//这里的a被强制转换为了int类型
很简单,不需要多介绍。
&&必须左右两个边同时为真才为真,||只需要一边为真即为真。
注意一点小细节,&&如果左边不为真的话右边计算机就不再继续执行了。
条件操作符(三目操作符):
exp:expression表达式。判断表达式1是否成立,如果为真即输出表达式2,假即输出表达式2。
表达式可以为任意表达式,我们运用的非常广泛。
逗号表达式,它将两个及其以上的式子联接起来,从左往右逐个计算表达式,整个表达式的值为最后一个表达式的值。
如:(3+5,6+8)称为逗号表达式,其求解过程先表达式1,后表达式2,整个表达式值是表达式2的值。
下标引用不用过多介绍,数组里面常常会用到。符号[]。
函数调用就是main()中的符号()。
结构体成员访问操作符 “.” “ ->”,等到讲解结构体会提到的。
二:常见关键字
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;
}
static:
在C语言中:
static翻译过来就是静态的,被static修饰过的量都存放在静态区。在讲解static之前先介绍一下变量的创建与销毁。auto—自动的,我们之前有了解过量出了定义域以后就会自动销毁,同样,在量进入时会自动调用auto为量分配内存,而我们不需要去调用它,计算机自动调用。
而被static修饰后,量哪怕是出了定义域了都不会被销毁,只有当程序结束时,他才会销毁。
1.修饰局部变量:
例:
#include <stdio.h>
void test()
{
int i = 0;
i++;
printf("%d ", i);
}
int main()
{
int i = 0;
for(i=0; i<10; i++)
{
test();
}
return 0;
}
每一次调用test()函数就创建一个新的i,函数结束i就被销毁。
这是变量没有被static修饰的结果。
#include <stdio.h>
void test()
{
//static修饰局部变量
static int i = 0;
i++;
printf("%d ", i);
}
int main()
{ int i = 0;
for(i=0; i<10; i++)
{
test();
}
return 0;
}
test()函数只创建一个i,知道整个程序结束时i才会被销毁。
总结:static修饰局部变量改变了变量的生命周期,让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。
2.修饰全局变量:
//add.c
int g_val = 2018;
//test.c
int main()
{
printf("%d\n", g_val);//g_val定义在不同的文件里
return 0;
}
结果输出为2018
//add.c
static int g_val = 2018;
//test.c
int main()
{
printf("%d\n", g_val);
return 0;
}
这里会出现编译异常。g_val未被定义
这是因为,g_val被static修饰过后只允许在本文件中使用,不允许其他文件调用。
结论:一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使。
//add.c
int Add(int x, int y) {
return c+y;
}
//test.c
int main()//分别定义在两个不同的源文件中
{
printf("%d\n", Add(2, 3));
return 0;
}
结果输出为5
比较被static修饰的结果
//add.c
static int Add(int x, int y) {
return c+y;
}
//test.c
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}
与之前的一样,编译器会报错。
总结:一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。
三:#define 定义常量和宏
我们的常量除了之前定义方法以外,还有其他的定义方法。就是这里的#define修饰
#define NUM 50
并且写在代码的第一行,由于它是个常量,我们一般将他的所有字母大写。
宏是用于定义函数的,宏的定义同样也是需要写在第一行,但是也是有区别的,我们所定义的宏是有参数的。例:
//define定义宏
#define ADD(x, y) ((x)+(y))
(x,y)叫做宏的参数,((x)+(y))叫做宏体。
#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);
printf("sum = %d\n", sum);
return 0;
}
结果为 5 50
其实宏和函数还是特别像的,不同在于实现的定义不同,宏的参数是不需要参数类型的,而函数缺少参数则不行。
至于为什么宏体内部的x,y需要加括号,是因为他们是一个整体,可以为表达式,所以需要用括号括起来。
四:指针
1.内存
内存 | 地址 |
一个字节 | 0X1122 |
一个字节 | 0X1133 |
.... | .... |
一个字节 | 0X0000 |
#include <stdio.h>
int main()
{
int num = 10;
#//取出num的地址
//注:这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)
printf("%p\n", &num);//打印地址,%p是以地址的形式打印
return 0;
}
结果如上。至于为什么会有EF是因为计算机这里是使用十六进制储存地址的。
那么问题又来了,我们需要如何储存该地址呢?所以就提出了定义指针变量。
int num = 10;
int *p;//p为一个整形指针变量
p = #
p是个变量,用于储存num的地址。
#include <stdio.h>
int main()
{
int num = 10;
int *p = #
*p = 20;
return 0;
}
当然每个类型都有一个对应的指针类型
char ch='m';
char* pc = &ch;
----------------
double db=546.5;
double* pc=&db;
*是指针的意思,也称之为解引号。指针变量是用来存放内存地址的变量,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。
2.指针变量的大小:
指针变量的大小取决于地址的大小 ,如果是32位平台下地址是32个bit位(即4个字节)
int main()
{
printf("%d\n", sizeof(char *));
printf("%d\n", sizeof(short *));
printf("%d\n", sizeof(int *));
printf("%d\n", sizeof(double *));
return 0;
}
其实这里不该用%d,%d只是用来取整数的,这里改用%zd。
%zd是强制转化为整形的格式输出符,对应的是size-t类型的(c99)中规定 ,不过有些编译器可能不支持%zd,它可能只支持%u或者%lu。
五:结构体
我们的数组可以用于储存同类型的数据,而我们的结构体可以用于储存不同类型的数据,一个结构体内部可以含有很多种类型,同时可以为数组。
在C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体的定义形式为:
struct 结构体名{
结构体所包含的变量或数组
};
例如:描述一个学生,该学生的名字,学号,年龄,性别,等等。
struct Stu
{
char name[20];//名字
int age; //年龄
char sex[5]; //性别
char id[15]; //学号
};
注意大括号后面的分号
;
不能少,这是一条完整的语句。
结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据。
像 int、float、char 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型;而结构体可以包含多个基本类型的数据,也可以包含其他的结构体,我们将它称为复杂数据类型或构造数据类型。
对成员的初始化:
#include <stdio.h>
struct Stu
{
char name[20];//名字
int age; //年龄
char sex[5]; //性别
char id[15]; //学号
};
int main()
{
struct Stu s = {"小吴", 20, "男", "202146535"};
return 0;
}
除了这种整体初始化以外,还可以逐个初始化
- stu.name = "Tom";
- stu.age = 18;
结构体成员的获取:
结构体和数组类似,也是一组数据的集合,整体使用没有太大的意义。对于数组,我们使用操作符 [ ] 来获取,其中的某一个元素,对于结构体,我们使用 “ . ”或者“ ->”来获取结构体中的某个成员,我们将其称之为结构体成员访问操作符。
同样是结构体访问操作符,二者还是有些区别的:
“ . ”操作符左边的操作数是一个“结构体”的表达式,而“ -> ”操作符左边的操作数是一个指向结构体的指针。
例:
为了使用方便和直观,C语言允许把(*s).name用ps->name来替换,也就是说(*s).name和ps->name是等价的。
所以在结构体中“ . ”和“ -> ”的用法相似,但是并不等价。