C语言不得不说的那些知识点

前言

有些小伙伴可能没有C的基础,看我前三篇博文有些吃力。所以本篇我先放下C++,给大家讲一下C里边我认为比较重要,而且到了C++也还是会用到的知识点,方便大家理解C++的原理。

其实在我前面的博文也多少提到了一点,什么函数啊,实参形参啊,传递参数靠拷贝啊,局部变量啊,指针啊,这些多多少少都提到了,而且也有详细讲的。其实这些都是学C语言的时候必讲的知识点,可见C是C++的前置技能点。有时间的同学建议还是系统地学一下C,没有时间的话可以看我的博客也是可以的:)

另外,要知道C++本身就是用C来写的,所以没有必要刻意去区别这两种语言。还有就是顺序选择循环这三大结构,学任何语言都是必定会说的,在这篇博文我就不多做说明了,基础中的基础。

数据类型

内存单元

在讲数据类型之前先来讲讲内存单元。一个内存单元的大小是一字节(Byte),也就是8比特(bit),也就是一串8位的二进制数,如0000 0010表示十进制数中的2。

然后每个内存单元有一个地址,用于单独标识一个内存单元。地址的长度由机器的系统决定,32位的机器地址长度就是4字节,64位就是8字节。地址通常用十六进制来表示,0x1e就是个十六进制数,前面的0x的意思是省略前面的0。进制转换这里就不多说了,但是还是建议大家去补补计算机组成原理。

如果不理解上面我说的,那我打个比方:内存单元就是一间房子,里面可以放东西,然后每个房子有自己唯一的地址。

数据类型与长度

上面说了一个内存单元能放一个字节的东西,那么“字节”就成了度量数据长度的单位。长度为4意思就是4个字节,也就是4*8=32bit,也就是32位二进制数。每个数据类型都有一定的长度,在代码里可以用sizeof()函数查看。这里给出C语言数据类型及其长度和范围参照表:

整数

类型名称长度取值范围
char1-128 ~ 127
unsigned char10 ~ 255
short2-32768 ~ 32767
unsigned short20 ~ 65535
int4-2147483648 ~ 2147483647
unsigned int40 ~ 4294967295
long4 / 8
unsigned long4 / 8
long long8-9.2233720368548e+18 ~ 9.2233720368548e+18
unsigned long long80 ~ 1.844674407371e+19

这里long数据类型有点特殊,在Windows平台,如果是有Visual Studio编程的同学,用sizeof(long)看到的长度是4,而在Linux用gcc编译的话输出的就是8。我推测应该是和编译器有关,因为微软的Visual Studio用的是自家的编译器。具体取值范围参考上面的int和下面的long long。

那这些取值范围是怎么来的呢?这里有有些涉及到计算机原理的知识了,这里简单讲讲。用char来举例,他最短,1个字节就是8位二进制,8位二进制能表示28个数字,也就是256个数字。如果是无符号字符型,也就是unsigned char,256个数字就是[0, 255],这个范围里边正好256个数;如果是有符号数,那就对半分,128个用来表示正数,另外128个用来表示负数。欸那么就有的同学就问了:那0呢,0怎么办?确实,包括0的话就257个数了。那就正数少表示一个,所以有符号char表示的范围就是[-128, 127]。

当然我这么说明是不严谨的,只是为了大家好理解,大致意思差不多。真正要解释的话,得去看看计算机组成原理了,但对于初学者就没有必要太深入了。

浮点数

类型名称长度取值范围
float4±3.4e38(精确到6位小数)
double8±1.7e308(精确到15位小数)
long double12±1.19e4932(精确到18位小数)

浮点数就是小数,范围的计算方法和整数不同,更加复杂。感兴趣的同学还是去看看计算机组成原理,不感兴趣就记一下长度和精度就好了。

简单的内存模型

我们来简单看看一个变量在内存中是怎么放的,不过这涉及到大端字节序(big endian)和小端字节序(little endian)的问题。这里就不过多深入,为了演示简单,就用小端字节序和比较短的short来演示。假设有以下代码:

short a = 2;

那这个变量a在内存中是怎么存储的呢?首先2转化为二进制是10,然后short的长度是2,一共是16位,不够的用0补全(负数的话操作不一样,具体去了解一下原码补码反码),那就是0000 0000 0000 0010。将他放进内存就是这样的:
简单的内存模型

数组

数组的使用

数组是用于储存多个相同数据类型的集合,用下表来访问集合中的各元素。用法如下:

int array[2];	//声明一个长度为2的数组
array[0] = 1;	//定义0号元素
array[1] = 2;	//定义1号元素

cout << array[1] << endl;	//2

以上代码还能修改成这样:

int array[2] = { 1, 2 };	//初始化数组

中括号里面放初始化数组的值,多一个都不行,少了的话少了的那部分会被初始化为0。

当然如果你不确定数组的长度,也可以这样进行初始化:

int array[] = { 1, 2 };		//数组长度为2

注意,中括号只能使用在数组初始化的时候,千万不能这么用:

int array[2];
array = { 1, 2 }	//error

数组的内存模型

数组就是一块连续的内存空间。还是用小端字节序和short,用以上的例子,我来画个简单的内存模型:
数组内存模型

数组与指针

指针在我前面的博文也说了,就是一个内存地址。而且其本质上就是个整型变量,也占用内存单元的。长度的话就看看地址有多长,32位机就是4字节,64位机就是8字节。可以直接输出看看他的庐山真面目:

int a = 10;
int *ptr = &a;
cout << ptr << endl;	//0x7fff41e70914

64位机的话输出的十六进制一般都会超过8个字符(4位二进制表示一位十六进制),0x表示前面的0省略了。

那么数组有和指针有什么关系呢?我们来看以下代码:

int array[2] = { 1, 2 };
cout << array << endl;
cout << array + 1 << endl;
cout << *array << endl;
cout << *(array + 1) << endl;

输出如下:

[dyamo@~/code 17:12]$ g++ -o array.exe array.cpp 
[dyamo@~/code 17:12]$ ./array.exe 
0x7ffcc5579408
0x7ffcc557940c
1
2

可知其实array就是一个指针,他指向数组头号元素在内存中的位置。然后两个地址相差正好是4(十六进制里c代表12),这正好是一个int型的长度,可以知道数组占据的内存单元确实是连续的。同时也可以知道,指针的加减操作,其实就是让指针前移或者后移一个数据类型的长度。

字符串

什么是字符

引用百度百科的定义:

字符指类字形单位或符号,包括字母、数字、运算符号、标点符号和其他符号,以及一些功能性符号。字符是电子计算机或无线电通信中字母、数字、符号的统称,其是数据结构中最小的数据存取单位,通常由8个二进制位(一个字节)来表示一个字符。 字符是计算机中经常用到的二进制编码形式,也是计算机中最常用到的信息形式。

简单讲就是有一个字符表,上面一个数字对应一个字符。所以在计算机里,字符的本质还是一个数字,只不过输出显示的时候,计算机会根据这个数字去字符表里面找到对应的字符,然后输出给你看。具体去了解一下字符编码,这里给出一个最简单,也是学C语言会学的字符编码方式——ASII编码。下面是ASII码表:
在这里插入图片描述
这里简单说一下,我们用单个字符的时候都是用单引号括起来的,如‘A’,这样编译器看到之后会将其解释为对应ASII码表里面的编号,也就是65。所以其实下面这两行代码没什么区别:

char a = 'A';
char b = 65;

所以不要以为给char赋值一个100,输出之后就是100。因此字符型到整型的强制转换,也是转换成对应数值:

char a = 'A';
int b = (int)a;	//b == 65

还有就是大写字母转化成小写字母,要加上32而不是26,因为大小写字符中间隔着几个字符。另外数字字符要真正转化成数字,记得减去字符‘0’或者48。

字符型数组

有了数组的知识之后,我们就可以聊字符串了。字符串实际上就是一个字符型数组(下面说到字符串指的就是字符型数组),这个数组里面存储着一个个字符,并且以’\0’(对应ASII码表的0号字符)作为字符串的结尾。我们来看一个简单的字符串:

char str[4] = { 'a', 'b', 'c', '\0' };
printf("%s\n", str);	//abc

在输出的时候,输出函数会先找到这个字符型数组的首地址,从首地址开始一直往下读取并输出,直到遇到’\0’。所以可想而知,如果忘记用’\0’标记结尾,你们输出函数就会一直输出下去。至于输出的是什么就看你的内存里放着什么东西了,大部分都是随机的东西,所以输出就是一堆乱码。所以千万要记住用’\0’标记结尾。

另外还可以这样子进行初始化:

char str[] = "hello world!";

用双引号括起来的就是字符串了,编译器会自动在最后加上’\0’的。

字符数组处理函数

使用字符数组处理函数需要包含头文件string.h。这里就不一一细说了,就简单讲讲函数原型及其功能就好了,一看就能明白:

  • 计算字符串长度函数:int strlen(char *str);
    顾名思义,就是计算字符数组长度的。该函数从首字符地址一直往后扫,直到遇到’\0’,然后返回统计的长度。
  • 字符串复制函数:void strcpy(char *str1, char *str2); void strncpy(char *str1, char *str2, int n);
    作用就是将字符串str2复制并覆盖字符串str1,后者则可以设定复制str2的前n个字符。(最好保证str1有足够的空间)
  • 字符串连接函数:void strcat(char *str1, char *str2); void strncat(char *str1, char *str2, int n);
    将字符串str2连接字符串str1之后,后者则可以设定取str2的前n个字符。可以想象,就是从str1的结束标记’\0’,开始将str2给复制过去。(最好保证str1有足够的空间)
  • 字符串比较函数:int strcmp(char *str1, char *str2);
    根据长度和字典序比较两个字符串的大小(如"abc" < “abd”,还有"aa" < “aaa”),str1比str2大会返回正数,等于会返回0,小于则返回负数。
  • 字符串转换为数值函数:int atoi(char *str); float atof(char *str);
    顾名思义,就是将数字字符串转换真正的数字。

以上的字符串处理函数(除了最后的字符串转换为数值函数)都是C语言里面比较老而且很危险的函数,在Windows如果用Visual Studio这个IDE来写代码的话编译直接不通过,Linux的话用gcc/g++编译倒是能通过,运行起来直接报段错误。我学习C语言的时候是用Codeblocks这个IDE来写代码的,那个倒是不会报错。就最后的字符串转换为数值函数能用,其他了解一下就行了。

由于以上原因,C++封装了一个安全且十分方便的字符串类string,使用时包含string头文件,然后使用命名空间std就行。上面的功能除了字符串转数值,其他全都有,十分的方便。

结构体

结构体struct也是C语言中十分重要的关键字,在数据结构课程中也经常用他来封装数据结构。之前我的博文也讲了,C++的类class就是用C的struct来实现的。在C++中,两者的唯一区别就是class的默认访问权限是private而struct是public,仅此而已。不过一般我们使用结构体,只是用来封装数据结构,如果是C++的话最多就再多写一个构造函数,就不会写其他东西了。

因此我也不用过多的笔墨再介绍了,就简单看看使用吧,上代码:

#include <stdio.h>
#include <stdlib.h>

typedef struct student {
	char *name;
	char *id;
	unsigned int score;
} student;

int main(){
	student stu1;
	stu1.name = "张三";
	stu1.id = "1234";
	stu1.score = 66;
	printf("姓名:%s\n学号:%s\n成绩:%d\n", stu1.name, stu1.id, stu1.score);

	student *stu2 = (student *)malloc(sizeof(student));
	stu2->name = "李四";
	stu2->id = "4321";
	stu2->score = 77;
	printf("姓名:%s\n学号:%s\n成绩:%d\n", stu2->name, stu2->id, stu2->score);
	free(stu2);
	return 0;
}

在结构体的定义中,要用typedef给结构体起一个别名,不然编译会报错,也不知道为啥。这里我用了一样的名字,不想重新起名字了。

C的话是只能封装数据,不能写额外的东西,C++就可以在里面加个构造函数,而且也不用typedef。

然后在main函数里,是两种定义结构体对象的两种方法,和类的实例化是一样的。如果是结构体对象的话,就用【.】这个运算符来访问成员变量;如果是结构体对象指针的话,就用【->】来访问,这点也和类的对象一样。

malloc和free函数我的上一篇博文也具体讲了,他们的作用和C++关键字new / delete差不多,都是为对象分配 / 回收堆上内存。不同点在于malloc函数只是单纯地划分一块区域出来,你给的参数是多少,他就给你划多少字节区域。然后malloc的返回值是void *指针,想要使用的话还得强制转化成结构体指针。free函数也很直接,就是直接回收。对比new / delete,他们会调用类的构造 / 析构函数,并且new会直接返回对象指针,不用强制转换。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值