文章目录
- C语言深入解剖
- 1.关键字
- 1.5 最冤枉的关键字sizeof理解
- 1.6 signed、unsigned关键字
- 1.7 if else组合
C语言深入解剖
1.关键字
1.1 最宽宏大量的关键字 — auto
相关:一般在代码中所定义的变量,即局部变量,默认是auto所修饰的,但一般都是省略的。不是所有的变量都是auto,它一般用来修饰局部变量。
nt main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf(" i = %d\n", i);
if (1)
{
auto int j = 0; //自动变量(局部变量)
printf("1. j = %d\n", j);
j += 1;
printf("2. j = %d\n", j);
}
}
return 0;
}
1.2 最快的关键字 — register
CPU主要进行计算硬件单元,为了方便计算,一般要先把数据从内存中读取到CPU内,所以CPU是需要具备一定的数据临时存储能力。在现代CPU内,集成了一组叫寄存器的硬件,用来进行临时数据的保存。寄存器在硬件层面上来看,它能够有效地提高运算效率,因为不需要从内存中读取数据。
register修饰变量
什么样的变量,可采用register?
1.局部的
2.不被写入的
3.高频被读取的
4.使用时不能大量使用,寄存器数量有限
#include <stdio.h>
int main()
{
register int x = 0;
printf("x = %d\n", &x);
//编译后会报错,register修饰的变量不能取地址。
//不是所有的编译器都会报错,目前用的vs2019报错了。
return 0;
}
1.3 最名不副实的关键字 — static
相关:
static修饰全局变量时,该变量只能在本文件内访问,不能被外部文件直接访问。
static修饰局部变量时,该变量的生命周期改变,作用域不变。
static修饰函数时,该函数只能在本文件内被访问,不能在外部文件直接访问。
static有助于项目的维护,提供安全的保证。
//main.c
#include <stdio.h>
extern int b;
extern int Max(int x, int y);
//static void fr()
//{
// int i = 0;
// printf("i = %d\n", i );
// i += 1;
//}
//局部变量i,因为居于临时性
//函数调用时会开辟空间并初始化
//函数结束后释放空间
static void fr()
{
static int i = 0;
printf("i = %d\n", i);
i += 1;
}
int main()
{
static int a = 10;
printf("a = %d\n", a );
printf("b = %d\n", b);
int i = 0;
for (i = 0; i < 10; i++)
{
fr();
}
return 0;
}
//test.c
#include <stdio.h>
static int b = 20;
static int Max(int x, int y)
{
if (x > y)
return x;
else
return y;
return 0;
}
1.4 基本数据类型
相关:
定义一个变量,是需要类型的,类型决定了开辟空间的大下。
#include <stdio.h>
int main()
{
printf("%d\n", sizeof(char)); //1
printf("%d\n", sizeof(short)); //2
printf("%d\n", sizeof(int)); //4
printf("%d\n", sizeof(long)); //4
printf("%d\n", sizeof(long long)); //8
printf("%d\n", sizeof(float)); //4
printf("%d\n", sizeof(double)); //8
return 0;
}
变量命名规则
1.命名直观且可读,便于阅读和记忆。标识符最好采用英文单词或其组合,不许使用拼音。
2.命名长度应符合“min-length && max - information”原则。如MinVal比MinValueUnitOverflow好用。
3.当标识符由多个词所组成,每个词的第一个字母大写,其他全部小写。如:int MaxVal;。
4.尽量避免名字中出现数字编号,比如Val1,Val2等,除非逻辑上需要编号。
5.在多个文件之间共同使用的全局变量或函数要加范围限定符,如TEST_等。
6.作用域前缀命名,比如标识符:Global Variable,作用域前缀:g。
7.数据类型前缀命名,比如int iMax = 10;。
8.程序中不得出现仅靠大小写区分的相似的标识符。如:int x, X;int o , O, int i , I;
9.一个函数名禁止被用于其他处。如:
void test(int x)
{
int a = 1;
}
void pr(void)
{
int test = 2;
}
10.所有宏定义、枚举常数、只读变量全用大写字母进行命名,使用下划线分割。如:#define INT_MAX 100
11.局部变量中可采用通用命名方式,仅限于n、j、k等作为循环变量使用。如:i、j、k。
12.结构体被定义时必须有明确的结构体名
13.定义变量的同时别忘记初始化。
14.不同类型数据之间的运算注意精度扩展问题,一般低精度向高精度扩展。
1.5 最冤枉的关键字sizeof理解
1.5.1 常年被误认为函数
#include <stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof (a)); //1
printf("%d\n", sizeof(int)); //2
printf("%d\n", sizeof a); //3
printf("%d\n", sizeof int); //4
//1、2、3是正确的
//4是错误的,int是关键字不能直接使用sizeof关键字
//sizeof不是函数,它是关键字或是操作符
return 0;
}
1.5.2 sizeof(int)*p表示什么意思
#include <stdio.h>
int main()
{
int* p = NULL;
int arr[10] = { 0 };
int* test[2];
printf("%d\n", sizeof(p)); //4
printf("%d\n", sizeof(arr)); //40
printf("%d\n", sizeof(test));//8
return 0;
}
1.6 signed、unsigned关键字
有符号整数与无符号整数
char
unsigned char
signed char
short
unsigned short[int]
signed short[int]
int
unsigned int
signed int
long
unsigned long[int]
signed long[int]
原码、反码、补码
创建一个变量需要在内存中开辟空间,空间的大小由不同的类型所决定,数据在所开辟内存中如何存储呢?
有符号数:
#include <stdio.h>
int main()
{
int a = 10;
//0000 0000 0000 0000 0000 0000 0000 1010
int b = -20;
//1000 0000 0000 0000 0000 0000 0001 0100 原码
//1111 1111 1111 1111 1111 1111 1110 1011 反码
//1111 1111 1111 1111 1111 1111 1110 1100 补码
//补码->原码
//方法1:先-1,然后符号位不变,按位取反。
//方法2:先符号位不变,按位取反,然后+1。
//对于有符号数,它一定是能够表示数值为正数或负数的,一般最高比特位表示符号位。
//在计算机中有三种有符号数的表示方式:原码、反码、补码。
// 任何数据在计算机中,都需要转换成二进制,因为计算机只认识二进制。
//三种表示方式都有符号位和数值位两部分,其中符号位用1表示‘负’,使用0表示‘正’,数值位三种表示方式各有不同。
//如果一个数值为负数,那么它应该按照原码->反码->补码进行转化。
//原码:将二进制按照正负数形式转换成二进制即可。
//反码:原码的符号位不变,其他按位取反。
//补码:反码+1。
//如果一个数值为正数,那么它的原码、反码和补码都相同。
return 0;
}
无符号数:不需要进行转化,也不需要符号位,原反补相同。
对整型来说:数据在内存中存放的是补码。
补充:
#include <stdio.h>
int main()
{
unsigned int y = -10;
//1000 0000 0000 0000 0000 0000 0000 1010 原
//1111 1111 1111 1111 1111 1111 1111 0101 反
//1111 1111 1111 1111 1111 1111 1111 0110 补
printf("%d\n", y); //-10
printf("%u\n", y); //4294967286
//变量如何存?
//数字需先转为补码,放入空间中,看数据是否有+-号,与变量是否有无符号无关。
//
//变量如何取?
// 一定要看变量本身的类型,决定是否要看符号位。如果不用,直接将二进制转为十进制。需要的话,则需要转成原码。
return 0;
}
大小端:
大端:按字节为单位,低权值位数据存储在高地址处,为大端。
小端:按字节为单位,低权值位数据存储在低地址处,为小端。
为什么使用补码?
计算机系统中,数值一律使用补码来表示和存储。因为,使用补码,可以将符号位和数值域一起处理,同时加减法也可一起处理(CPU只有加法器,没有减法器)。另外补码跟原码之间的相互转换,运算过程是一样的,不需要额外的硬件。
取值范围
char
unsigned char: [0,2^8-1]
signed char: [-2^7, 2^7-1] //char
//数据的取值范围,本质是多为比特位所构成的排列组合的数量。
//规律:
//无符号:[0, 2^n-1]
//有符号:[-2^(n-1),2^(n-1)-1]
问题:
//qst1
#include <stdio.h>
int main()
{
char a[1000];
for (int i = 0; i < 1000; i++)
{
a[i] = -1 - i; //‘\0’(0)为结束
}
printf("%d",strlen(a)); //255
return 0;
}
//qst2
#include <stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i+j); //-10
return 0;
}
//qst3
#include <stdio.h>
int main()
{
unsigned int i;
for (i = 0; i >= 0; i++)
{
printf("%u\n",i); //死循环
}
return 0;
}
1.7 if else组合
基本语法
语法结构:
//1
if (表达式)
语句;
//2
if (表达式)
语句1;
else
语句2;
//3
if (表达式1)
语句1;
else if (表达式2)
语句2;
else
语句3;
补充
1.C语言中’0‘表示假,非’0‘表示为真。
2.if语句的执行,它会先完成表达式中的值,得到逻辑结果,再进行判断。
1.7.1 bool变量与’零值‘进行比较
c语言中是否存在bool类型?
在c99之前,主要c90没有, 目前大部分书中都认为是没有的。但c99中引入了_Bool类型,_Bool就是一个类型,在头文件stdbool.h中,被宏重新写成了bool,为了保证兼容性。
#include <stdio.h>
#include <stdbool.h>
int main()
{
bool test = true;
test = false;
printf("%d\n", sizeof(test)); //1
return 0;
}
//源码
// stdbool.h
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The C Standard Library <stdbool.h> header.
//
#ifndef _STDBOOL
#define _STDBOOL
#define __bool_true_false_are_defined 1
#ifndef __cplusplus
#define bool _Bool
#define false 0
#define true 1
#endif /* __cplusplus */
#endif /* _STDBOOL */
1.7.2 float类型变量与’零值‘进行比较
#include <stdio.h>
int main()
{
double x = 3.2;
printf("%.50f\n", x); //3.20000000000000017763568394002504646778106689453125
return 0;
}
//补充:浮点数在内存中存储不是完整存储的,十进制转化为二进制,可能会有精度损失。
//损失不是一定就减少了,也有可能增多。
结论
1.浮点数在进行比较时,不可直接用哦’=='进行比较。
2.浮点数本身有精度损失,导致各种结果可能存在微小的差别。
两个浮点数该怎么比较?
//应该进行范围精度的比较
//1
if ((x - y) > -精度 && (x - y) < 精度)
{
;
}
//2 - 简洁版
if (fabs(x - y) < 精度)
{
;
}
//fabs:浮点数求绝对值。
//使用之前需要包含头文件<math.h>
//精度:可用系统精度。
//使用需包含以下头文件
#include <float.h>
DBL_EPSILON //double 最小精度
FLT_EPSILON //float 最小精度
修改后:
#include <stdio.h>
#include <math.h>
#include <float.h>
int main()
{
double x = 1.0;
double y = 0.1;
printf("%.50f\n", x - 0.9);
printf("%.50f\n", y);
if (fabs((x - 0.9) - y) < DBL_EPSILON)
{
printf("OK\n");
}
else
{
printf("NO\n");
}
return 0;
}
//两个精度的定义
#define DBL_EPSILON 2.2204460492503131e-016 // smallest such that 1.0+DBL_EPSILON != 1.0
#define FLT_EPSILON 1.192092896e-07F // smallest such that 1.0+FLT_EPSILON != 1.0
float类型变量与’0‘的比较
#include <stdio.h>
#include <float.h>
#include <math.h>
int main()
{
double x = 0.00000000000000000000001;
//if(fabs(x-0.0) < DBL_EPSILON){ //1
//if(fabs(x) < DBL_EPSILON){ //2
if (x > -DBL_EPSILON && x < DBL_EPSILON)
{
printf("OK\n");
}
else
{
prinf("NO\n");
}
return 0;
}
1.7.3 指针变量与’零值‘进行比较
int* p = NULL;
if (p == 0); if (p != 0); //1
if (p); if (!p); //2
if (p == NULL); if (p != NULL); //3
1.7.4 else与哪个if配对?
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
if (a == 3)
if (b == 4)
printf("1\n");
else
printf("2\n");
return 0;
}
//推荐
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
if (a == 3)
{
{
if (b == 4)
printf("1\n");
}
}
else
{
printf("2\n");
}
return 0;
}
1.8 switch、case、break
语法结构
switch(表达式)
{
case 常量表达式1: 语句1;break;
case 常量表达式2: 语句2;break;
'
'
'
case 常量表达式n: 语句n;break;
default: 语句n+1;
}
//补充:
#include <stdio.h>
int main()
{
int x = 1;
switch (x)
{
case x: //错误!
printf("OK\n");
break;
default:
printf("NO\n");
break;
}
return 0;
}
1.9 do、while、for
//while
while(表达式){
循环体语句;
}
//for
for(表达式1;表达式2;表达式3){
循环体语句;
}
//do-while
do{
循环体语句;
}
while(表达式);
补充:
//三种循环所对应的死循环写法
while(1)
{
;
}
for(;;)
{
;
}
do
{
;
}while(1);
问题:
#include <stdio.h>
int main()
{
while (1)
{
char c = getchar();
if (c == '#')
{
break;
}
printf("%c\n", c); //将\n删去即可
}
return 0;
}
1.9.1 break与continue区别
break:
continue:
1.10 goto关键字
#include <stdio.h>
int main()
{
int i = 0;
for (i = 1; i <= 2; i++)
{
if (i == 2)
{
goto end;
}
printf("Hello ");
}
end:
printf("World!\n");
return 0;
}
1.11 void关键字
void能否定义变量?
#include <stdio.h>
int main()
{
void a; //err
return 0;
}
为什么void不能定义变量?
定义变量的本质是开辟空间。void作为空类型,理论上来说是不应该开辟空间的,就算开了空间,也仅作为一个占位符所看待。所以既然无法开辟空间,那么也无法作为正常变量所使用,编译器也就不让它定义变量。
1.11.1 void修饰函数返回值和参数
//s1
#include <stdio.h>
void print()
{
printf("******\n");
printf("******\n");
printf("******\n");
}
int main()
{
print();
return 0;
}
//补充:如果定义一个函数或者库函数不需要返回值的话,那就可以写成void。
//如果不写void,自定义函数默认返回值是int。
//如果不写void也会让阅读代码的人产生误解,忘记写了,还是默认int呢?
//s2
#include <stdio.h>
int test1() //函数默认不需要参数
{
return 1;
}
int test2(void)//明确表明函数不需要函数
{
return 1;
}
int main()
{
printf("%d\n", test1(2)); //依然传入参数,编译器不会警告或报错。
printf("%d\n", test2(2)); 依然传入参数,编译器会警告或报错。
return 0;
}
//补充:如果一个函数不需要参数,可以将参数列表设置为void,可以将错误明确地发现,也能让阅读代码的人看出意思。
1.11.2 void指针
#include <stdio.h>
int main()
{
void* p = NULL; //可以
return 0;
}
void*是指针,能够明确空间大小。
#include <stdio.h>
int main()
{
void* p = NULL;
int* a = NULL;
float* b = NULL;
p = a;
p = b; //类型不同,但编译器没报错
return 0;
}
//总结:void*可用来接受任何指针类型。
1.11.3 void*所定义的指针变量能否进行运算操作
//vs2019
#include <stdio.h>
int main()
{
void* p = NULL;
p++; //err
p--; //err
p += 10; //err
return 0;
}
//gcc
#include <stdio.h>
int main()
{
void* p = NULL; //NULL在数值层面上来说,是0
p++;
printf("%d\n", p); //1
p += 10;
printf("%d\n", p); //11
return 0;
}
//为什么不同的平台下,编译器的结果有所不同呢?
//因为大部分的编译器都是标准c,Linux是扩展c,Linuc同时也能保证标准c的运行无误。
1.12 return关键字
#include <stdio.h>
char* show()
{
char str[] = "hello world";
return str;
}
int main()
{
char* a = show();
printf("%s\n", a); //乱码
return 0;
}
//return语句不可返回指向栈内存的‘指针’,因为该内存会在函数体结束时自动销毁。
以上代码的解释:
调用函数,形成栈帧。
函数返回,释放栈帧。
扩展1:
在计算机中,释放或者说是清除空间是否需要将数据全部清0/1?
清空数据,只需要将该数据设置为无效即可。
扩展2:
临时变量为什么有临时性?
临时变量要在一个变量栈帧结构中开辟空间并保存变量,栈帧结构在函数调用完毕后,要被释放掉。
1.13 const关键字
1.13.1 const修饰变量
#include <stdio.h>
int main()
{
const int i = 10;
i = 20; //报错,变量i不可直接被修改
return 0;
}
const修饰的变量并不是不可被修改:
#include <stdio.h>
int main()
{
const int i = 10;
int* p = (int*)&i;
*p = 20;
return 0;
}
const修饰变量的意义:
1.让编译器保护那些不希望被修改的参数,防止无意代码的修改。
2.告诉读代码的人,声明一个参数,是为了告诉用户这个参数的应用目的。
const修饰的变量,不能作为数组定义的一部分:
#include <stdio.h>
int main()
{
const int n = 10;
int arr[n]; //报错
return 0;
}
1.13.2 const修饰一般变量
const int i = 1;
int const i = 1;
1.13.3 const修饰数组
int const arr[5] = {1,2,3,4,5};
const int a[5] = {1,2,3,4,5};
1.13.4 const修饰指针
const int * p; //p可变,p指向的对象不可变
int const * p; //p可变,p指向的对象不可变
int * const p; //p不可变,p指向的读写可变
const int * const p; //指针p和指向的对象都不可变
1.13.5 const修饰函数的参数
void fun(const int *p);
1.13.6 const修饰函数的返回值
#include <stdio.h>
const int* test()
{
static int g_val = 100;
return &g_val;
}
int main()
{
int *a = test();
*a = 200;
printf("%d\n", *a);
return 0;
}
1.14 volatile关键字
volatile作用:
编译器对访问votile修饰的变量就不会再进行优化,从而可提供对特殊地址的稳定访问,保持内存可见性。
const volatile int a = 10;
//const负责写,volatile负责读(从内存),两者并不冲突。
1.15 extern关键字
extern作用:
声明函数或变量。
//test.h
#pragma once
#include <stdio.h>
extern int a;
extern void show();
//test.c
#include "test.h"
int a = 10;
void show()
{
printf("hello world!");
}
//main.c
#include "test.h"
int main()
{
show();
return 0;
}
1.16 struct关键字
空结构体多大:
在VS2013中不可直接定义空结构体,在linux环境下为0字节,不同的编译器,处理的结果不同。