最全C语言知识点——整合(持续更新)

一直以来都是零零散散的去书写一些学习C语言的相关知识点,今天决定将所学习的已经发表和没有发表的所有知识点进行一个整合,方便自己,也适用于更多学习C语言基础的同学,如有纰漏,欢迎指出,有则改之无则加勉。

第壹部分:初识C语言和基础知识

一:什么是C语言程序设计

1. 什么是C语言?

C语言是一门通用的计算机编程语言,广泛的应用于底层的开发。C语言的设计目标是提供一种能以简易的方式编译,处理低级存储器,产生少量的机器码以及不需要任何运行环境支持便能够运行的编程语言。

2. 第一个的C程序

#include <stdio.h>
int main()
{
printf("hello new world!\n");
return 0;
}

二:具体的数据类型

1. 都存在着那些类型?

char      字符数据类型
short     短整型
int       整形
long      长整型
long long 更长的整形
float     单精度浮点数
double    双精度浮点数

存在这么多的类型是为了更加丰富的表达我们在生活之中所遇见的各种值。

2. 每种类型的大小是多少?

这段程序则是为了我们更好的检验数据类型大小到底是多少最为正确的一种方式了。

#include <stdio.h>
int main()
{
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
printf("%d\n", sizeof(long double));
return 0;
}

当然我们也应该明白对于不同的系统(32位,64位)的话,所对应的数据类型的大小是不同的,因此对于很多时候我们所考虑的大小,是需要结合相关的计算机系统来判定的,下面我们直接给大家上图。
C语言中各类型数据的长度
一般来说对于这些相对的数据类型的大小也是要求我们尽可能地自己记住,避免在后期编程的时候出现问题。

3. 主要数据的范围

    类型       无符号(unsigned)           有符号(signedchar          0~+255                    -128~+127
    
    short        0~65535                  -32768~32768
    
    int        0~4294967295            -2147483648~+2147483648(21亿)
    

这是所罗列出我们使用频率尽可能会多一些的数据类型的取值范围,也是为了方便我们在后期更好的使用;除此之外,也是建议大家将2的(2-16)次方大概牢记一些,相信大多数朋友肯定应该是比较熟悉的 了吧。

4. 不同类型之间的转换

4.1 两种不同的类型转换

当操作数的类型不同,而且不属于基本数据类型时,经常需要将操作数转化为所需要的类型,这个过程即为强制类型转换。强制类型转换具有两种形式:显式强制转换和隐式强制类型转换。
4.1.1显式强制类型转换:通过一定语句和相关的前缀,将所给出的类型强制转换成为我们所需要的类型。

TYPE b = (TYPE) a;

其中,TYPE为类型描述符,如int,float等。经强制类型转换运算符运算后,返回一个具有TYPE类型的数值,这种强制类型转换操作并不改变操作数本身,运算后操作数本身未改变。
举例1

int n=0xab65char a=char)n;

上述强制类型转换的结果是将整型值0xab65的高端一个字节删掉,将低端一个字节的内容作为char型数值赋值给变量a,而经过类型转换后n的值并未改变。
4.1.2隐式强制类型转换:当算术运算符两侧的操作数类型不匹配的时候,会触发“隐式的转换”,先转换成相同的类型,之后再进行计算。
C在以下四种情况下会进行隐式转换:
1、算术运算式中,低类型能够转换为高类型。
2、赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并赋值给他。
3、函数调用中参数传递时,系统隐式地将实参转换为形参的类型后,赋给形参。
4、函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型,赋值给调用函数。

4.2 自动类型转换

在C语言中,自动类型转换遵循以下规则:
1、若参与运算量的类型不同,则先转换成同一类型,然后进行运算。
2、转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。
a、若两种类型的字节数不同,转换成字节数高的类型
b、若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型
3、所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
4、char型和short型(在visual c++等环境下)参与运算时,必须先转换成int型。
5、在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分直接舍去。
一些需要注意的东西:
类型转换的规则
类型转换阶层图
在强制类型转换的时候需要注意的是,如(double)a,则可能会丢失掉原有的精度。
想要实现运算之后的四舍五入计算,只需要给一个数加上0.5就可以直接完成。

5. 整形截断和整型提升

5.1 整型截断

把四个字节的变量赋值给一个字节的变量,会存在截断,则对应的截取它低位上的值:

四字节变量
int 0x11111111 11111111 11111111 11111111;

截断后

一字节变量
char 0x1111 1111;

5.2 何谓整形提升

C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

5.2 整型提升的意义

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int
的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这
种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned
int,然后才能送入CPU去执行运算。
如:

char a,b,c;
...
a = b + c;

b和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断,然后再存储于a中

5.3 如何进行整型提升

整形提升是按照变量的数据类型的符号位来提升的:
补充
( 因为在cpu之中只能够以

/负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111


/正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001


/无符号整形提升,高位补0

例1

int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if(a==0xb6)
printf("a");
if(b==0xb600)
printf("b");
if(c==0xb6000000)
printf("c");
return 0;
}

例子中的a,b要进行整形提升,但是c不需要整形提升 a,b整形提升之后,变成了负数,所以表达式
a= =0xb6 , b= =0xb600 的结果是假,但是c不发生整形提升,则表达式 c= =0xb6000000 的结果是真.
所程序输出的结果是:

c

例2

int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(!c));
return 0;
}

例2中的,c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.
表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof( c ) ,就是1个字节.

!!!截断提升相结合的例子!!!

#include <stdio.h>

int main()
{
	先进行截断
	原码 0x10000000 00000000 00000000 10000000
	反码 0x11111111 11111111 11111111 01111111
	补码 0x11111111 11111111 11111111 10000000
	a截断后内存中所存储的则是 1000 0000char a=-128;
    输出的是十进制,则意味着要进行提升(需要根据类型而定)
    0x11111111 11111111 11111111 100000000
    0x11111111 11111111 11111111 011111111
    0x10000000 00000000 00000000 100000000
	printf("%d\n",a);
	return 0;
}

三:常量和变量

1. 常量 变量的定义

生活之中有着一些值是保持不变的,比如圆周率,身份证,血型,性别等;而有些值却是可变的,比如我们的年龄,体重,薪资,和成绩。
在C语言之中,我们经常将那些不变的值定义为常量,也用常量这个概念名词来进行表示,那些变化的值我们使用变量这个概念名词来进行表示。

2. 常量

C语言之中的常量和变量的定义的形式是有所差异的。
C语言之中的常量可以分为以下几种:

  1. 字面常量
  2. const修饰的常变量
  3. #define定义的标识符常量
  4. 枚举常量
    一个程序就让大家直接明白这些到底都是什么意思!
#include <stdio.h>
enum Sex
{
MALE,//枚举常量
FEMALE,
SECRET
};
int main()
{
3.14;//字面常量
1000;//字面常量
const float pai = 3.14f; //const 修饰的常量
pai = 5.14;//ok?
#define MAX 100 //#define的标识符常量
return 0;

3. 定义变量的方法

int age = 150;
float weight = 45.5f;
char ch = 'w';

对于后半部分所定义的值式可以进行改变,是完全由编程者来进行定义的一种数据。

4. 变量的分类

4.1 局部变量

局部变量又称之为内部变量,是由某对象或者某个函数所创建的变量,只能被内部引用,不能够被其他函数或对象引用。

4.2 全局变量

被整个程序所使用的变量称之为全局变量。

#include <stdio.h>
int global = 2019;//全局变量
int main()
{
int local = 2018;//局部变量
//下面定义的global会不会有问题?
int global = 2020;//局部变量
printf("global = %d\n", global);
return 0;
}

5. 变量在函数里的使用

#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &a, &b);
sum = num1 + num2;
printf("sum = %d\n", sum);
return 0;
}
//这里介绍一下输入,输出语句 有一个了解
//scanf
//printf

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

6.1 作用域

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

  1. 局部变量的作用域是变量所在的局部范围。
  2. 全局变量的作用域是整个工程.

6.2 生命周期:

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

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

四:字符串 转义字符 注释

1. 字符串

"hello bit.\n"

1.1 这种由双引号(Double Quote)引起来的一串字符称为字符串字面值(String Literal),或者简称字符串。

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

2. 转义字符:

顾名思义是转变意思,我们要在屏幕上打印一个目录: c:\code\test.c 我们该如何写代码?

#include <stdio.h>
int main()
{
printf("c:\code\test.c\n");
return 0;
}

实际上程序的运行结果是这样的:
转义字符运行结果
由于它存在着’\t’ 和’\n’ 所以打印出爱的时候是不存在这两个转义字符的。

3. 都有那些转义字符

转义字符 释义
\? 在书中写连续多个问号时使用,防止他们被解析成为三个字母词
\’ 用于表示字符串常量 ‘
\’’ 用于表示一个字符串内部的双引号
\\ 用于表示一个反斜杠,防止它被解释为一个转义序列符
\a 警告字符,蜂鸣
\b 退格符
\f 进纸符
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ddd ddd表示1~3个八进制的数字。 如: \130
\xddd ddd表示3个十六进制数字。 如: \x030
#include <stdio.h>
int main()
{
//问题1:在屏幕上打印一个单引号',怎么做?
//问题2:在屏幕上打印一个字符串,字符串的内容是一个双引号“,怎么做?
printf("%c\n", '\'');
printf("%s\n", "\"");
return 0;
}

重点例题:不妨自己试试看

//程序输出什么?
#include <stdio.h>
int main()
{
printf("%d\n", strlen("abcdef"));
// \32被解析成一个转义字符
printf("%d\n", strlen("c:\test\32\test.c"));
return 0;

4. 注释

  1. 代码中有不需要的代码可以直接删除,也可以注释掉
  2. 代码中有些代码比较难懂,可以加一下注释文字
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
/*C语言风格注释
int Sub(int x, int y)
{
return x-y;
}
*/
int main()
{
//C++注释风格
//int a = 10;
//调用Add函数,完成加法
printf("%d\n", Add(1, 2));
return 0;
}

注释有两种风格:

  1. C语言风格的注释 /xxxxxx/
    缺陷:不能嵌套注释
  2. C++风格的注释 //xxxxxxxx
    可以注释一行也可以注释多行

五:内部存储结构

1. 内部存放形式

因为CPU只能够进行加法运算,是不是有点打破很多小程序猿固有的思维啊,所以对于整数来说,在内存之中是以补码的形式存放的。
符号位的表示

2. 原码反码补码

正数:原码是其本身 反码 补码和原码是完全相同的
  7  原码 0000 0111
     反码 0000 0111
     补码 0000 0111
 
负数:原码是其本身 反码符号位不变,其余位全部取反,补码则是在反码的基础上加1
 -7   原码 1000 0111
      反码 1111 1000
      补码 1111 1001
零:零分为正零+0 和负零 -0
  +0  原码 0000 0000
      反码 0000 0000
      补码 0000 0000
  -0  原码 1000 0000
      反码 1111 1111
      补码 0000 0000

3. 大小端

3.1 在计算机系统中是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit,但在C语言中还有其他类型的存在,因此要对这些多字节安排问题就导致了大端存储和小端存储。
3.2 大小端的故事
根据我了解到的好像是一个国家在吃一个食物的时候,众大臣讨论到底应该从大的一头开始吃,还是从小的一头开始吃起。
3.3 大小端区别
大端是和我们的认知是相符合的,则是从低到高开始进行排列。
小端的话则和我们的认知相反,它是从小往大的排列。

int 19 
在内存中的存储以大端序排列则为 00 01 00 11;
在内存中的存储以小端序排列则为 11 00 01 00

一个代码检测自己的电脑到底是大端序还是小端序

#include <stdio.h>

int main(){
    int x=1;
    char c=(char)x;
    if(c==1)
    {
    	printf("小端机\n");
    }
    else if(c==0)
    {
    	printf("大端机\n");
    }
    return 0;
}

4. 什么是ASCII码

ASCII 是我们所看见的字母和数字在内存之中所在的十进制地址值

每一部分总结练习题和代码链接:(之后会附上 )

第贰部分:操作符 关键字 表达式

一:操作符

1. 算数操作符

 +-*/%  求余
  1. 除了%操作符之外,其他的几个操作符是可以作用于整数和浮点数的。
  2. 对于 / 操作符,如果两个操作符都为正数的话,执行整数除法,而只要有浮点数存在就执行浮点数除法。
  3. % 操作符的两个操作符规定必须为整数,返回的则是整除之后所得到的余数。

2. 移位操作符

<< 左移操作符
>> 右移操作符
  1. 左移操作符移位规则:
    左边抛弃,右边补0
int num=10;

num<<1  (左移一位)

左移

  1. 右移操作符移位规则:
    首先右移运算分两种:
    1. 逻辑移位 左边用0填充,右边丢弃
    2. 算术移位 左边用原该值的符号位填充,右边丢弃

      例题
      逻辑右移:左边补0;
      逻辑右移
      算术右移:左边用原该值的符号位进行填充,由于是负数,所以符号位为1,即左边补1
      算术右移

3. 位操作符

位操作符有:

&  //按位与
|  //按位或
^  //按位异或
注:他们的操作数必须是整数

简单练习题

#include <stdio.h>
int main()
{
int num1 = 1;
int num2 = 2;
num1 & num2;
num1 | num2;
num1 ^ num2;
return 0;
}

高难度练习题
要求:不能够创建临时变量(第三个变量),实现两个数的交换。

#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a^b;
b = a^b;
a = a^b;
printf("a = %d b = %d\n", a, b);
return 0;
}

4. 赋值操作符

赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。

int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值
这样的代码感觉怎么样?
那同样的语义,你看看:
x = y+1;
a = x;
这样的写法是不是更加清晰爽朗而且易于调试。

4.1 复合赋值符

+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
这些运算符都可以写成复合的效果

例子

int x = 10;
x = x+10;
x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁

5. 单目操作符

单目操作符 含义
逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
- - 前置、后置- -
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
#include <stdio.h>
int main()
{
int a = -10;
int *p = NULL;
printf("%d\n", !2);
printf("%d\n", !0);
a = -a;
p = &a;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);//这样写行不行?
printf("%d\n", sizeof int);//这样写行不行?
return 0;
}

6. 关系操作符

>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等“

这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。
!!! 注意: = 和== 的意义是不同的,用时需慎重

7. 逻辑操作符

&&    逻辑与
||    逻辑或

区分逻辑与和按位与 区分逻辑或和按位或

1&2----->0
1&&2---->1
1|2----->3
1||2---->

8. 条件操作符

exp1 ? exp2 : exp3
if (a > 5)
b = 3;
else
b = -3;
转换成条件表达式,是什么样?
2.使用条件表达式实现找两个数中较大值。

二:关键字

1. 常见关键字

auto break case char const continue default do
double else enum extern float for goto if int
long register return short signed sizeof
static struct switch typedef union unsigned void 
volatile while

2. 关键字typedef

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;
}

3. 关键字static

在C语言中:
static是用来修饰变量和函数的

3.1 修饰局部变量

代码1
#include <stdio.h>
void test()
{
int i = 0;
i++;
printf("%d ", i);
}
int main()
{
for(i=0; i<10; i++)
{
test();
}
return 0;
}
//代码2
#include <stdio.h>
void test()
{
static int i = 0;
i++;
printf("%d ", i);
}
int main()
{
for(i=0; i<10; i++)
{
test();
}
return 0;
} 

对比代码1和代码2的效果理解static修饰局部变量的意义。
结论:static修饰局部变量改变了变量的生命周期,让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。

3.2 修饰全局变量

代码1
//add.c
int g_val = 2018;
//test.c
int main()
{
printf("%d\n", g_val);
return 0;
}
代码2
//add.c
static int g_val = 2018;
//test.c
int main()
{
printf("%d\n", g_val);
return 0;
}

代码1正常,代码2在编译的时候会出现连接性错误。
结论:一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。

3.3 修饰函数

代码1
//add.c
int Add(int x, int y)
{
return c+y;
}
//test.c
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}
代码2
//add.c
static int Add(int x, int y)
{
return c+y;
}
//test.c
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}

代码1正常,代码2在编译的时候会出现连接性错误。
结论:一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。

4. define定义常量和宏

define定义标识符常量
#define MAX 1000
define定义宏
#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;

使用宏的话:整体而言,宏的本质相当于文本的替换

  1. 定义一个常量
  2. 借助宏来重定义一个类型的别名
  3. 宏还能够影响到编译器的行为
  4. 宏还能够定义一个代码片段(类似于函数的效果)

三:表达式

1. 逗号表达式

exp1, exp2, exp3, …expN

逗号表达式,就是用逗号隔开的多个表达式。 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?


代码2
if (a =b + 1, c=a / 2, d > 0)


代码3
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
a = get_val();
count_val(a);
}
如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
//业务处理
}

2.下标引用

[ ] 下标引用操作符
操作数:一个数组名 + 一个索引值。

int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9

3. 函数调用

( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}

4. 结构成员

访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名

#include <stdio.h>
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
}

四:语句和函数

未完待续(先收藏起来之后慢慢看)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章