C语言-变量和数据类型1
变量
数据类型
const 常量
内存
计算机将将所有东西存储的方式为二进制,对于计算机来说它会将其中所有的内容用二进制存入到内存当中。每一个东西在计算机中都是一段二进制编码方便计算机输入和输出。
说到内存我们就不得不提存储容量的换算单位。
- 1Byte = 8Bit
- 1KB = 1024Byte
- 1MB = 1024KB
- 1GB = 1024MB
- 1TB = 1024GB
- 1PB = 1024TB
- 1EB = 1024PB 在二进制存储当中我们通常的换算关系为1024l。
变量
变量就是在内存中找一块区域用来存放我们自己定义的变量,从而达到方便存取的目的。
int a;
这样代表这个区域存放的是一个整数,这里的 int 是 integer 的缩写,意思是一个整数,a 呢,是我们给这个内存区域取的名字,当然它可以叫任何名字,当然C语言中会有变量的命名规则,我以后会讲到。
现在我们只是为这个变量取了一个名字,并没有将任何数据放进去。C语言使用这种方式将数据放入到我们起好名字的内存空间中去:
a=123;
这里利用了一个=
号,在C语言中的作用和数学中的作用不尽相同,而C语言中,这种过程称之为赋值
。(赋值就是将数据存放到内存的过程)
这里的 a 我们将其赋值成123,我们也可以将其第二次赋值,利用同样的方法我们可以重新赋值,将原来的值覆盖成新的值,而原来的值也就找不回来了。
因为 a 的值可以改变,我们给它起了一个形象的名字变量(Variable)
。
int a;
创造了一个变量 a,我们把这个过程叫做变量定义
,a=123;
把123交给变量a,这个过程叫做给变量赋值
;又是因为是第一次赋值我们也称变量初始化
、赋初值
。
有两种方式可以赋初值:
可以先定义再赋值
int b;
b=123;
也可以在定义时直接赋初值
int b=123;
第二种方式会有一点要注意,在拥有多个变量的时候,我们不能在赋初值时采用连等的形式,例如:
int a=b=c=123;
如果想要以这种方式定义初值,可以:
int a=123,b=123,c=123;
数据类型
内存中数据有多种解释方式,使用之前必须确定,上面定义的int
型,就代表着整形,而整形只是数据类型中的一种:
字符型 | 短整型 | 整形 | 长整型 | 单精度浮点型 | 双精度浮点型 | 无类型 |
---|---|---|---|---|---|---|
char | short | int | long | float | double | void |
1 | 2 | 4 | 4 | 4 | 8 |
数据类型用来说明数据的类型,确定了数据的解释方式,让计算机和程序员不会产生歧义。
我们也可以定义多个数据类型的变量,例如:
int a,b,c;
float m=10.9, n=123.456;
char p, q='A';
最后强调一下,数据类型只在定义变量时声明,而且必须指明;使用变量时无需再指明,因为此时数据类型已经确定了。
在屏幕上输出各种类型数据
我们之前学过的puts函数,它只能用来输出字符串,不能输出其它类型的数据,所以这里我们就要用到另一个函数printf。
printf
比puts
更加强大,它不仅仅可以输出字符串,还可以输出整数,小数,单个字符等,并且输出格式也可以自己定义。
printf是print format
缩写,意思是“格式化打印”,也就是打印在屏幕上,所以我们也通常将它称为格式化输出
。
printf使用方法
#include <stdio.h>
int main(int argc,char *argv[])
{
int a=200,b=300,c=400;
printf("a=%d, b=%d, c=%d",a,b,c);
}
这 %d称为格式控制符,它指明了以什么形式输出数据,格式控制字符都是以 % 开头,后跟其它字符
- %d: 输出一个整数。
- %c: 输出一个字符。
- %s:输出一个字符串。
- %f:输出一个小数。
sizeof操作符
sizeof
操作符是用来获取某个数据类型或变量所占用的字节数。
#include <stdio.h>
int main(int argc, char* argv[])
{
short a=10;
int b=100;
printf("%d,%d,%d,%d",sizeof(a),sizeof(b),sizeof(long),sizeof(char));
}
在不同的系统中,数据类型的长度是显示不同的长度的
不同整形的输出
- %hd 输出short int 类型
- %d 输出int类型
- %ld 输出long类型
二进制,八进制,十六进制
赋值方式
进制 | 二进制 | 八进制 | 十六进制 |
---|---|---|---|
赋值方式 | 0b或0B | 0 | 0x或0X |
输入输出
进制 | 八进制 | 十进制 | 十六进制 |
---|---|---|---|
short | %ho | %o | %lo |
int | %hb | %b | %lb |
long | %hx | %x | %lx |
我们要是想要输入输出时,标明是什么进制的数,则在 % 后加一个 # 号,当然这个东西不一定所有编译器都适用。
#include <stdio.h>
int main(int argc, char* argv[])
{
short a = 0b1010110;
int b = 02713;
int c = 0X1DAB83;
printf("%#ho,%#o,%#ho\n",a,b,c);
printf("%#hd,%#d,%#hd\n",a,b,c);
printf("%#hx,%#x,%#hx\n",a,b,c);
printf("%#hX,%#X,%#hX\n",a,b,c);
}
这段程序的运行结果就是:
0126,02713,07325603
86,1483,1944451
0x56,0x5cb,0x1dab83
0X56,0X5CB,0X1DAB83
C语言中的正负数输出
在C语言中以内存的最高位定为符号位,而也就是我们常说的首位为符号位,而在二进制代码符号位规定中0代表正数,1代表负数。将其放到首位后就能看出这段数值是正数还是负数。
当我们在运算时用不上负数时,我们也可以不用设置符号位,也就是说没有了负数,也就是说我们能表达的数值范围更大了。在定义变量时我们将数值类型前加上unsigned
用来表示无符号数。
short | int | long | unsigned short | unsigned int | unsigned long | |
---|---|---|---|---|---|---|
八进制 | – | – | – | %ho | %o | %lo |
十进制 | %hd | %d | %ld | %hu | %u | %lu |
十六进制 | – | – | – | %ho | %o | %lo |
有读者可能会问,我在上面也用了%o和%x来输出有符号数了,为什么没有发生错误呢?这是因为:
- 当以有符号数的形式输出时,printf就读取数字所占用的内存,并把最高位作为符号位,把剩下的内存作为数值位;
- 当以无符号数的形式输出时,printf也会读取数字所占用的内存,并把所有的内存都作为数值位对待。
简单的来说就是不管有符号还是无符号,只要第一位是0就永远不会出错。
强调一下不管以什么方式输出他都不会显示输出错误,只是对内存的解释不同罢了,那些格式控制符根本就不会关心你定义时到底是定义的有符号的还是无符号的,它只管输出:
- 你让我输出无符号数,那我在读取内存时就不区分符号位和数位值了,把所拥有的内存都看作数位值。
- 你让我输出有符号数,那我在读取内存时就把最高位定位符号位,其他的定位数值为然后输出。
在这里我们要强调一下:
- %d以十进制形式输出有符号的数
- %u以十进制形式输出无符号的数
- %o以八进制形式输出无符号的数
- %x以十六进制形式输出无符号的数 要记住printf并不支持以八进制和十六进制输出有符号位的数。
下面给大家一个实例应该就明白了:
#include <stdio.h>
int main()
{
short a = 0100; //八进制
int b = -0x1; //十六进制
long c = 720; //十进制
unsigned short m = 0xffff; //十六进制
unsigned int n = 0x80000000; //十六进制
unsigned long p = 100; //十进制
//以无符号的形式输出有符号数
printf("a=%#ho, b=%#x, c=%ld\n", a, b, c);
//以有符号数的形式输出无符号类型(只能以十进制形式输出)
printf("m=%hd, n=%d, p=%ld\n", m, n, p);
return 0;
}
运行结果:
a=0100, b=0xffffffff, c=720
m=-1, n=-2147483648, p=100
const 常量定义
有时候我们希望我们有一种变量它的值不能被改变,整个作用域中都保持固定。 创建常量的格式为:
const type name = value;
#include <stdio.h>
int getNum(){
return 100;
}
int main(){
int n = 90;
const int MaxNum1 = getNum(); //运行时初始化
const int MaxNum2 = n; //运行时初始化
const int MaxNum3 = 80; //编译时初始化
printf("%d, %d, %d\n", MaxNum1, MaxNum2, MaxNum3);
return 0;
}
运行结果: 100, 90, 80
拓展一些知识:
const和指针
const也可以和指针变量一起使用,这样可以限制指针变量本身,也可以限制指针指向的数据。const和指针一起使用会有几种不同的顺序。
const int *p1;
int const *p2;
int * const p3;
在最后一种情况下,指针是只读的,也就是 p3 本身的值不能被修改;在前面两种情况下,指针所指向的数据是只读的,也就是 p1、p2 本身的值可以修改(指向不同的数据),但它们指向的数据不能被修改。
当然,指针本身和它指向的数据都有可能是只读的,下面的两种写法能够做到这一点: 纯文本复制
const int * const p4;
int const * const p5;
const 和指针结合的写法多少有点让初学者摸不着头脑,大家可以这样来记忆:const 离变量名近就是用来修饰指针变量的,离变量名远就是用来修饰指针指向的数据,如果近的和远的都有,那么就同时修饰指针变量以及它指向的数据。
const 和函数形参
在C语言中,单独定义 const 变量没有明显的优势,完全可以使用#define命令代替。const 通常用在函数形参中,如果形参是一个指针,为了防止在函数内部修改指针指向的数据,就可以用 const 来限制。
在C语言标准库中,有很多函数的形参都被 const 限制了,下面是部分函数的原型:
size_t strlen ( const char * str );
int strcmp ( const char * str1, const char * str2 );
char * strcat ( char * destination, const char * source );
char * strcpy ( char * destination, const char * source );
int system (const char* command);
int puts ( const char * str );
int printf ( const char * format, ... );
我们自己在定义函数时也可以使用 const 对形参加以限制,例如查找字符串中某个字符出现的次数:
#include <stdio.h>
size_t strnchr(const char *str, char ch){
int i, n = 0, len = strlen(str);
for(i=0; i<len; i++){
if(str[i] == ch){
n++;
}
}
return n;
}
int main(){
char *str = "https://tianlangz.top";
char ch = 't';
int n = strnchr(str, ch);
printf("%d\n", n);
return 0;
}
运行结果: 3
根据 strnchr() 的功能可以推断,函数内部要对字符串 str 进行遍历,不应该有修改的动作,用 const 加以限制,不但可以防止由于程序员误操作引起的字符串修改,还可以给用户一个提示,函数不会修改你提供的字符串,请你放心。
const 和非 const 类型转换
当一个指针变量 str1 被 const 限制时,并且类似const char *str1这种形式,说明指针指向的数据不能被修改;如果将 str1 赋值给另外一个未被 const 修饰的指针变量 str2,就有可能发生危险。因为通过 str1 不能修改数据,而赋值后通过 str2 能够修改数据了,意义发生了转变,所以编译器不提倡这种行为,会给出错误或警告。
也就是说,const char *和char *是不同的类型,不能将const char *类型的数据赋值给char *类型的变量。但反过来是可以的,编译器允许将char *类型的数据赋值给const char *类型的变量。
这种限制很容易理解,char *指向的数据有读取和写入权限,而const char *指向的数据只有读取权限,降低数据的权限不会带来任何问题,但提升数据的权限就有可能发生危险。
C语言标准库中很多函数的参数都被 const 限制了,但我们在以前的编码过程中并没有注意这个问题,经常将非 const 类型的数据传递给 const 类型的形参,这样做从未引发任何副作用,原因就是上面讲到的,将非 const 类型转换为 const 类型是允许的。
下面是一个将 const 类型赋值给非 const 类型的例子:
#include <stdio.h>
void func(char *str){ }
int main(){
const char *str1 = "tianlangz.top";
char *str2 = str1;
func(str1);
return 0;
}
第7、8行代码分别通过赋值、传参(传参的本质也是赋值)将 const 类型的数据交给了非 const 类型的变量,编译器不会容忍这种行为,会给出警告,甚至直接报错。