3

C语言基础总结篇(究极避坑)

 2 years ago
source link: https://blog.csdn.net/xiaoxiaoguailou/article/details/120938704
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1.typedef:类型重命名

一切合法的变量名的定义(普通变量、指针变量、数组变量、函数指针、结构体)都可以用typedef转换成类型名

加typedef之前

unsigned int UINT;	//普通变量
int* PINT;			//指针变量
int Array[10];		//数组变量
void (*pfun)();		//函数指针
struct Student stu;	//定义结构体变量stu
struct Student *pstu;//定义结构体指针pstu

加上typedef之后

typedef unsigned int UINT;	//UINT类型名
typedef int* PINT;			//PINT指针类型名
typedef int Array[10];		//Array数组类型名
typedef void (*pfun)();		//函数指针类型名
typedef struct Student stu;	//stu类型
	//使用stu s1;
typedef struct Student *pstu;	//结构体指针类型
	//使用:pstu p1 = NULL;

(1)给已有的类型名起别名

typedef unsigned char u_int8;
typedef unsigned short u_int16;
typedef unsigned int u_int32;
typedef unsigned double u_int64;

(2)对已有的声明,变量名的定义加上typedef 变成类型名

#include <stdio.h>
typedef int Arr[10];
int main()
{
    Arr a = { 1, 2, 3, 4, 5 };
    for (int i = 0; i < 5; i++)
    {
        printf("%4d ", a[i]);
    }
    printf("\n");
}

结果:

在这里插入图片描述

结构体经典写法:

#include <stdio.h>

struct Student
{
    ...;
    
}stu, *pstu;

//这样是定义了stu结构体变量和pstu结构体变量指针

//前面加上typedef后
typedef struct Student
{
   ...; 
    
}stu, *pstu;
//stu便成了自定义的结构体类型
//pstu变成了自定义的结构体指针的类型

//从而可以使用该类型进行定义变量

2.请问p和q的类型

int* p, q;

结果:

*和变量名结合,不是与类型名结合,所以p是int指针类型,q是int类型;
在这里插入图片描述

结合1,想同时定义p和q两个指针:

#include <stdio.h>
typedef int *PTR;

int main()
{
 	PTR p,q;
    return 0;
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iHyypOrk-1635073955441)(C:\Users\小小怪咯\AppData\Roaming\Typora\typora-user-images\image-20211014154707137.png)]

3.关键字sizeof

  1. sizeof是一个关键字,在编译期间确定类型和大小;
#include <stdio.h>

int main()
{
    int a = 0;
    int x;
    
    //在编译期确定
    x = sizeof(++a);
    //等价于x = 4;
    printf("a = %d\n", a);
    return 0;
}

//结果a = 0
  1. sizeof和strlen()的区别
  • 调用时机不同:sizeof是关键字,编译期间确定类型和大小

    ​ strlen()是函数,在运行期间调用函数

  • 功能不同:strlen()是专门计算字符串的长度;

    ​ sizeof在计算字符串所占用的空间大小;

char buff[] = {"helloworld"};
int len = strlen(buff); //len = 10;
int size = sizeof(buff); // size = 11;

4、进制数转换的贪心算法

5、c/c++的常变量不同侧重点

vs2019的全局变量未初始化默认为0,局部变量未初始化是随机值,使用该值编译不通过

  • c中的常变量侧重与"变量",不能使用常变量定义数组,编译期不通过;

  • c++中的常变量侧重于"常",可以使用该常变量定义数组;

  • C++常变量类似于宏,却有不同与宏

    • 编译时期不同
      • 与宏有所不同:宏在预编译时进行宏替换;使用常变量定义数组是在编译期进行确定的;
    • 是否存在类型和占用空间
      • 宏不存在类型,不占用空间
      • 常变量有类型,占用空间
    • 安全性
      • 宏不存在类型,没有类型检查,不安全
      • 常变量有数据类型,有类型检查,比较安全

6、’ ’ 和" "

’ ‘是字符的定界符, 在’前面加上\后转义变成单引号字符–》\’

char ch = ''';	//error ''是定界符,想使用单引号字符需要转义
char ch = '\'';	//true

""是字符串的定界符

7、ascii码值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-guE4NOl4-1635073955442)(C:\Users\小小怪咯\AppData\Roaming\Typora\typora-user-images\image-20211023171614795.png)]

8、转义字符

在这里插入图片描述

9、关于0 ‘0’ ‘\0’

int main()
{
    char cha = 0;		//ASCII值为0对应的字符就是空字符
    char chb = '0';		//字符0对应的ASCII值48
    char chc = '\0';	//等于cha == 》空字符
    char chd = ' ';		//空格字符ascii值是32
	return 0;    
}

10、关于\000 和\xff

\000将八进制数000转换成十进制对应的ascii码值,码值对应的字符;

  • 其中八进制数有效范围000~377,因为char一字节最大取值255,其对应的八进制数就是377;八进制数超出该范围编译器就会报错
char str[] = {"pzj\141hello"};	
//八进制的141转换成十进制的97	//pzjahello
//如果是"pzj\1411hello"	//pzja1hello
//如果是"pzj\148hello"		//只会转义\14因为8超出0~7
//如果是"pzj\889hello"		//此时的\就会被省略
	
int len = strlen(str);				//len = 9

\x00将十六进制的00转换成十进制对应的ASCII码值,码值对应的字符;

  • 其中十六进制数的有效范围是0~ff,因为char最大255,对应的十六进制数就是ff;十六进制数超出该范围也会报错

    char str[] = {"hello\61xworld"};	//helloaworld
    

10、字符串与\0

字符串的printf("%s")打印、strcpy拷贝、strcat连接、等函数都是以字符串的\0作为结束条件

char str[] = {"hello\0world"};
int size = sizeof(str);		//size = 12
int len = strlen(str);		//len = 5
	
printf("%s\n", str);		//hello

11、宏和字符串

#define MAX 1000
int main()
{
    char str[] = {"helloMAX"};
    
    printf("%s\n", str);	//helloMAX
}
  • 原因:MAX是字符串的一部分,不是标识符,不会被宏替换

12、char ch = 'abcd’问题

C++有一个叫做“多字符文字”的东西。'1234'就是一个例子。他们有类型int,它是实现–定义了它们所具有的值以及它们可以包含多少字符。

那算不了什么直接与字符被表示为整数的事实有关,但在实现中,很有可能'1234'定义为:

'1' + 256 * '2' + 256 * 256 * '3' + 256 * 256 * 256 * '4'
'4' + 256 * '3' + 256 * 256 * '2' + 256 * 256 * 256 * '1'

13、作用域(可见性)和生存期

  • 作用域:针对的是编译和链接的过程
    • 函数、全局变量从定义起(整个文件可见)全局可见,没有局部函数一说
  • 生存期(生命期):针对的是程序的执行过程
    • 局部变量的生存周期:函数被调用开始,函数执行结束时消亡,释放存储空间。存储在.stack区
    • 全局变量的生存期:从程序开始运行时开始,到程序执行结束时结束。存储在.data区
    • 动态生命期(堆区空间):标识符由特定的函数调用或运算来创建和释放,如果调用malloc()为变量分配存储空间开始,free()释放存储空间结束。存储在堆区.heap

编译错误:g_value未定的标识符

#include <stdio.h>
void Test()
{
 	int a = g_value;   
}
int g_value = 10;

int main()
{
    Test();
    return 0;
}

错误理解:误认为程序一边编译一边运行,g_value存在于.data段

14、C语言运算符优先级

优先级运算符名称或含义使用形式结合方向说明1[]数组下标数组名[常量表达式]左到右()圆括号(表达式) 函数名(形参表).成员选择(对象)对象.成员名->成员选择(指针)对象指针->成员名2-负号运算符-表达式右到左单目运算符(类型)强制类型转换(数据类型)表达式++自增运算符++变量名 变量名++单目运算符–自减运算符–变量名 变量名–单目运算符*取值运算符*指针变量单目运算符&取地址运算符&变量名单目运算符!逻辑非运算符!表达式单目运算符~按位取反运算符~表达式单目运算符sizeof长度运算符sizeof(表达式)3/除表达式 / 表达式左到右双目运算符*乘表达式*表达式双目运算符%余数(取模)整型表达式%整型表达式双目运算符4+加表达式+表达式左到右双目运算符-减表达式-表达式双目运算符5<<左移变量<<表达式左到右双目运算符>>右移变量>>表达式双目运算符6>大于表达式>表达式左到右双目运算符>=大于等于表达式>=表达式双目运算符<小于表达式<表达式双目运算符<=小于等于表达式<=表达式双目运算符7==等于表达式==表达式左到右双目运算符!=不等于表达式!= 表达式双目运算符8&按位与表达式&表达式左到右双目运算符9^按位异或表达式^表达式左到右双目运算符10|按位或表达式|表达式左到右双目运算符11&&逻辑与表达式&&表达式左到右双目运算符12||逻辑或表达式||表达式左到右双目运算符13?:条件运算符表达式1? 表达式2: 表达式3右到左三目运算符14=赋值运算符变量=表达式右到左/=除后赋值变量/=表达式*=乘后赋值变量*=表达式%=取模后赋值变量%=表达式+=加后赋值变量+=表达式-=减后赋值变量-=表达式<<=左移后赋值变量<<=表达式>>=右移后赋值变量>>=表达式&=按位与后赋值变量&=表达式^=按位异或后赋值变量^=表达式|=按位或后赋值变量|=表达式15,逗号运算符表达式,表达式,…左到右

易错点:

int main()
{
	int a = 1, b = 2;
	a *= b + 5;             //+的优先级高于 *= 所以 a = a * (b + 5) --> a = 7
	
	printf("%d\n", a);		//7
}

15、指针存储——小端存储

小端存储:高位数存放在高地址,低位数存放在低地址;

数值存储和地址存储都遵循小端存储

在这里插入图片描述

16、标准输入文件0、标准输出文件1、标准错误输出文件2

当一个程序开始运行时,默认会打开这三个文件;

  • 标准输入文件stdin:对应的文件描述符为0,通过某种映射关系将键盘输入映射成标准输入文件;stdin在内存上是有行缓冲区的,当遇到换行(’\n’)才会输入到缓冲区;
  • 标准输出文件stdout:对应的文件描述符为1,通过某种映射关系将屏幕输出映射成标准输出文件;stdout在内存上是有行缓冲区的,当遇到换行(’\n’)才会输出到屏幕;
  • 标准错误文件stderr:对应的文件描述符为2,是无缓冲区的,是直接输出在屏幕上;

程序案例:从键盘获取字符输出字符个数

17、宏和typedef

#define PINT int*    //宏替换,不考虑类型和大小
typedef int* TINT;	//类型重命名,会进行类型和大小识别

int main()
{
	PING a, b;  //int* a, b;
	TINT p, q;  //int* p; int* q;
}

18、extern关键字

extern用在全局变量或者函数的声明之前,用来说明“此变量、函数是在别处定义的,要在此处引用”;

使用情景:同一个工程下的不同文件

文件fun.c

int g_max = 10;
void fun()
{
    g_max +=10;
    printf("%d\n", g_max);
}

文件main.c

#include <stdio.h>
extern int g_max;
extern void fun();
int main()
{
    int a = g_max;
    fun(); 	   
}

C++中的extern的其他用法;

19、static关键字的使用

记忆函数:该函数中含有静态局部变量;

静态局部变量:当函数第一次被调用,函数中的局部静态变量被初始化,当这个函数被再次调用时,不会对该静态变量进行初始化,会保留上次函数执行结束后局部变量的值(作用域不变,生存期改变)

  • 注意:C语言的静态局部变量只能用常量进行初始化一次;

    C++可以用常量和变量进行初始化一次

问题解答:

  1. 形参能否加上static

    答:加上,编译通过,但是该变量是一个“坏”存储类;

    所以形参不加static

  2. 记忆函数是怎样实现第一次初始化的时候调用,后面不调用?

    答:在编译阶段,编译器将记忆函数中的静态局部变量存放在.data段中并给该变量一个记录值val = 1,当程序执行到定义静态局部变量的语句时,先对记录值进行判断,如果val == 1说明第一次调用,执行完毕后val–;否则val == 0 ,则跳过这条语句;

在这里插入图片描述

  • 注意:static int a = 10; 在多线程中需要考虑线程安全,多个线程同时执行该条语句,该值其中的val值会被同时拿到,这样就可能会多次执行该语句。单例模式的问题就需要考虑线程安全

静态全局变量:静态全局变量只能在当前文件中使用(作用域受限制,生存期不变)

  • 注意1:当全局变量、函数加上static后,作用域受限于本文件,其他文件无法访问;就算其他文件加上extern关键字声明也无法使用

    main.c文件

#include <stdio.h>
extern int g_max;
extern void fun();
int main()
{
    int a = g_max;  //编译报错,无法解析的命令g_max
	fun();
    return 0;
}
fun.c文件
static int g_max = 10;
static void fun()
{
    printf("%d\n", g_max);
}

注意2:希望fun.c文件中的const int a = 10; 常变量被其他文件调用,就在该变量定义前加上extern,同时使用的文件也要加上该变量的extern声明

main.c文件

#include <stdio.h>
extern int g_max;

int main()
{
    int a = g_max;  
	printf("%d\n", a);
    return 0;
}

fun.c文件

extern const int g_max = 10;	//外部可见的常变量

//extern static int g_max = 10;
​ //extern外部可见与static本文件可见矛盾

静态函数:static说明的函数字可以在当前c文件中使用(作用域受限,生存期不变)

20、4G的虚拟空间

在这里插入图片描述

21、数据在内存中存放的位置

#include <stdio.h>
int g_maxa = 10
int g_maxb;  
int g_maxc = 0;
static int g_maxd;       //默认是0
static int g_maxe = 0;
static int g_maxf = 10;
int main()
{
	int maxa = 10
    int maxb;
    int maxc = 0;
    static int maxd;
   	static int maxe = 0;
    static int maxf = 10;
}

22、const修饰定义的变量和#define宏替换的区别(见5)

  • 处理对象不同:const修饰的是定义的变量,而宏替换定义的是常量

  • 处理时期不同:const修饰的变量是在编译期间确定,宏替换是在预编译期进行替换;

  • 是否占用空间和有类型:const修饰的变量有大小和类型,宏替换的常量不占空间、不具有类型检查

23、浅谈宏函数

就是单纯的替换

#include <stdio.h>
#define SUM(x, y) x*y

int main()
{
    int a = 3, b = 4;
    int c = SUM(a + 1, b + 2);
    //int c = a+1*b+2
    printf("%d\n", c);
    return 0;
}
//
解决方案
/
#define SUM(x, y) (x)*(y)

哼哼~啊啊啊啊啊~结束啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦!!!!!!!!!!!!!!!!!!!!!!!!!!!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK