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

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.进制数转换的贪心算法

https://blog.csdn.net/xiaoxiaoguailou/article/details/120920622

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
    

11.字符串与\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

12.宏和字符串

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

13.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'

14.作用域(可见性)和生存期

  • 作用域:针对的是编译和链接的过程
    • 函数、全局变量从定义起(整个文件可见)全局可见,没有局部函数一说
  • 生存期(生命期):针对的是程序的执行过程
    • 局部变量的生存周期:函数被调用开始,函数执行结束时消亡,释放存储空间。存储在.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段

15.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
}

16.小端存储

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

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

在这里插入图片描述

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

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

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

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

18.宏和typedef

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

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

19.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的其他用法;

20.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 = 10static 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文件中使用(作用域受限,生存期不变)

21.4G的虚拟空间

在这里插入图片描述

22.数据在内存中存放的位置

#include <stdio.h>
int g_maxa = 10	//.data
int g_maxb;  	//.bss
int g_maxc = 0;	//.bss
static int g_maxd;    	//.bss   //默认是0
static int g_maxe = 0;	//.bss
static int g_maxf = 10;	//.data
int main()
{
    
    
	int maxa = 10;   //编译时期,当作指令存放在.text,运行阶段在栈上通过指令定义变量
    int maxb;		//.text
    int maxc = 0;	//.text
    static int maxd;  		//.bss
   	static int maxe = 0;	//.bss
    static int maxf = 10;	//.data
}

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

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

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

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

24.浅谈宏函数

就是单纯的替换

#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)

25.字符串和字符数组

区别:元素结尾有’\0’是字符串,没有就是字符数组

当定义数组没有给定大小时,同时使用花括号{}进行初始化存在以下两种形式

  • 字符串
char arr[] = {
    
    "helloworld"};	//字符串结尾自带'\0'
//sizeof(arr) ==  11
//strlen(arr) == 10
  • 字符数组(不要使用该种定义方式)
char brr[] = {
    
    'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'};
//结尾没有'\0'
//sizeof(brr) == 10
//strlen(brr) == ???	
//这里计算brr的长度的结果是错误的,brr结尾没有'\0'
//strlen()函数从&brr地址开始向后遍历,直到遇见'\0'才会结束,会导致指针越界

拓展:区分字符串常量和字符串

int main()
{
    
    
    //字符串
	char arr[] = {
    
    "hello"};
	char brr[] = {
    
    "hello"};

	//字符串常量
	char* p = "hello";
	char* q = "hello";

	printf("%d\n", arr == brr);// 0 两个变量的空间地址不同为假
	printf("%d\n", p == q);		//字符串常量使用的是同一块空间
}

结果:请添加图片描述

26.指针移动和*解引用

#include <stdio.h>

int main()
{
    
    
	//*和++优先级相同,从右向左结合
	int arr[5] = {
    
    12, 23, 34, 45, 56};
	int* p = arr;
	int x = 0;
	int y = 0;
	
	x = *p++;	//取出p先和*结合,然后p++
	y = *p;
	printf("%d, %d\n", x, y); // 12 23
	
	x = ++*p;	//*p 的值++  23+1 = 24
	y = *p;		// 24
	printf("%d, %d\n", x, y);	//24 24
	
	x = *++p;	//++p然后和*结合 34
	y = *p;		//34
	
	printf("%d, %d\n", x, y);	34 34
	return 0;
}

27.指针为什么要有类型

  • 指针自增自减操作时,编译器会根据指针的类型计算出移动的步长==sizeof(指针的类型) ;
  • 指针在解引用时,会根据指针的类型大小对内存间接访问的空间大小不同;

28.为什么需要二级指针

举个例子:通过下面程序能否成功修改p的指向???

#include <stdio.h>

int tmp = 20;
//指针的值传递,不能改变传递指针的指向
void fun(int* ptr)
{
    
    
	*ptr = 100;
	ptr = &tmp;		
	//修改的时ptr的指向但并没有修改p的指向
}



int main()
{
    
    
	int a = 10;
	int* p = &a;
	
	fun(p);		//ptr = p;等价于 p = &a
	printf("a = %d, *p = %d\n", a, *p);	
    //a = 100, *p = 100
	
}

结果:不能修改p的指向,修改的是形参ptr的指向,这个时候就需要用到二级指针

二级指针:指向指针的指针。实质也是一个指针,不过二级指针中保存的是一个一级指针的地址;

int tmp = 20;
//二级指针:修改传入指针的指向
void fun(int** ptr)
{
    
    
	**ptr = 100;	//两次解引用,最终结果a = 100;
	*ptr =  &tmp;	//一次解引用,等价于p = &tmp;
    /*成功将主函数中的指针p指向了全局部变量tmp,
      换句话说就是把tmp的地址存放到p中
	*/
}

int main()
{
    
    
	int a = 10;
	int *p = &a;
	fun(&p);	
    //ptr是一个二级指针,所以必须把指针p的地址传进函数,相当于ptr = p,
	
	printf("a = %d, *p = %d\n", a, *p);
    //结果 a = 100, b = 20
	return 0;
}

29.指针数组和数组指针

  1. 指针数组:对于下面程序来说,实质是一个存放int*类型的数组,数组中每个元素类型都是int*
int main()
{
    
    
    int a = 10;
	int b = 20;
	int* arr[] = {
    
    &a, &b};
	//arr[0]存放的是&a, arr[1]存放的是&b

	// 解引用
    //[]优先级高于* 所以arr[0]得到a的地址,再进行解引用
	*arr[0] = 100;
	*arr[1] = 200;

	printf("a = %d, b = %d\n", a, b);
    //结果 a = 100, b = 200;
}
  1. 数组指针:实际是一个指向数组整体的指针
#include <stdio.h>
int main()
{
    
    

	int arr[10] = {
    
     1,2,3,4,5,6,7,8,9 };
	int* p = &arr[0];
	//数组指针:是一个int[10] 类型的指针
	int(*parr)[10] = arr;
	//等价于int[10] *parr = arr;但是c语言不支持这种写法

	printf("%p,%p,%p\n", arr, p, parr);
	printf("%p,%p,%p\n", arr + 1, p + 1, parr + 1);

	//使用数组指针
	for (int i = 0; i < 10; i++)
	{
    
    
		printf("%d ", (*parr)[i]);
	}
	return 0;
}

结果:

请添加图片描述

30.const和指针变量的关系

int main()
{
    
    
    int const a = 10;
	//等价于const int a = 10;
    
    int* const p = &a;
    //const修饰p,所以不能修改p的指向,*p的值可以修改
    
    int const *p = &a;
    //等价于const int *p = &a;
    //const修饰*p,不能改变*p的值,可以修改p的指向   
}

31.函数指针

实际是一个指向函数入口地址的指针变量;

功能如下:

  • 使用函数指针直接调用函数
  • 函数指针作为其他函数的形参

代码示例:

#include <stdio.h>
int max(int x, int y)
{
    
    
    return x > y ? x : y;
}
int main()
{
    
    
    int a = 10;
    int b = 20;
    //定义并初始化函数指针
    int (*pfun)(int, int) = &max;
    
    //打印函数max的地址
    printf("%p\n", pfun);

    //使用函数指针
    int result = (*pfun)(a, b);
    printf("max = %d\n", result);
}

同时对应的指针函数,这个怎莫说呢,就是返回值是一个地址的函数

例如string.h头文件下的strcpy()函数,返回值就是一个char*类型的地址
哼哼~啊啊啊啊啊~结束啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦!!!!!!!!!!!!!!!!!!!!!!!!!!!

猜你喜欢

转载自blog.csdn.net/xiaoxiaoguailou/article/details/120938704