c语言基础补全

大学学了c之后,一直没有好好复习过,一些特性和语法都忘了,这里总结下。

一:一些关键字

1:typedf的用法
  • 给已知类型取别名
struct Person{
	char name[64];
	int age;
}
typedf struct Person myPerson;
typedf struct Person{
	char name[64];
	int age;
}myPerson;
  • 同样是给已知类型取别名,但用于指针类型的时候有特殊效果
//p1是char* 类型,p2是char类型
char* p1,p2;

//p1 p2 都是 char* 类型
typedf char* pChar
pChar p1,p2;

//p1 p2 都是 char* 类型
char *p1, *p2;
2:void类型

不能用void定义一个类型,因为编译器不知道分配多少个字节
但是 void* 是可以的,因为所有指针都是四个字节

//定义了一个无类型指针,所有类型指针的祖宗
void* a

void还可以用做 形参 和返回值

//虽然输入和输出都是void,但是这样写,可以明确的定义一个函数(但是c++不需要,如果没有传参,会直接报错)。
void func(void){};
3:预处理命令

这里 IF 0之后的代码是无效的

IF 0

代码

END IF
4:sizeof() 计算一个变量占用内存空间大小

1:返回值要考虑字节对齐的问题
2:返回值是 unsigned int,大部分编译器,使用无符号整型计算的结果也是无符号整型

//这里结果是 4294967286
unsigned int a = 10;
cout << a - 20 << endl;

//但是如果用printf,结果会不一样,结果是-10,因为%d会强行将无符号数变为有符号数
printf("%d",a-20);

数组本身是一个指向头元素的指针,当数组作为参数传递给函数时,计算的是指针的大小,如果不是作为函数参数,计算的是数组本身的大小。

int calArrSize(int arr[]){
    return sizeof(arr);
}
int main(){
    int a[] = {1,2,3,4,5,6,7};
    cout << sizeof(a) << endl;	//结果是28
    cout << calArrSize(a) << endl;	//结果是4
    return 0;
}
5:编译、extern、static

头文件不参与编译,在c语言中,每一个.c文件是一个编译单元,每一个编译单元是独立编译的,因此下面的代码不能够成功运行
文件A中

int a = 0;

文件B中

cout << a << endl;

B文件在独立编译时,不知道a变量的存在,因此会报错,如果需要通过编译,就需要使用extern关键字告诉编译器 a是存在的,在之后的链接过程中,链接器就会去找a。

extern int a;
cout << a << endl;

内部连接:变量只能在当前文件内使用
外部连接:变量可以被其他文件使用
本来全局变量是放在静态区的,可以被其他文件所访问,但如果使用 static 修饰全局变量,全局变量就变成了内部链接,只能被当前文件所访问。
所以,如果a确实没有定义,或者是static类型的变量,在链接的时候,还是会报错的。
extern 是单个声明,而头文件则是批量声明(include),编译的时候,会将头文件的内容直接拷贝进来。

6:常量区

(1):const全局和局部变量区别
const全局变量在常量区,常量区的变量都不能修改(直接或间接都不能修改)

const int a = 10;
int main(){
	a = 20; //报错,不能直接修改
	int* p = &a;
	*p = 20;//报错,也不能间接修改
}

const局部变量在栈上,栈上的值都是可以修改的(可以间接修改)

int main(){
	const int a = 100;
	a = 200;	//报错,不能直接修改
	int* p = &a;
	*p = 200;	//可以间接修改
}

(2):字符串常量
字符串常量都在常量区,常量区的变量名就是字符串本身的名字

int main(){
	char* p = "Hello world";
	printf("%d",&("Hello world"));
	printf("%d",p);
}

打印:

4214855
4214855

字符串常量能不能修改,看编译器,一般来说,不允许修改
字符串常量如果值相同,会不会共用一个,这个不同编译器也不一样

7:内存分区总结

总的来说,分为 代码区 和数据区
代码区:存放编译后的二进制代码,不可寻址
数据区:堆、栈、全局 \ 静态、常量(分字符串常量区和变量常量区)

二:指针和函数

1:宏函数

宏函数其实不是函数,类似c++的内联函数,编译的时候替换,由于不会有压栈操作,因此大多数时候比函数效率高

#define ADD(x,y) x+y

int main(){
    cout << ADD(1,2) << endl;
}
2:函数调用惯例

惯例指的是函数的调用者和被调用者对函数调用有着一致的理解
(1)函数参数的传递顺序和方式
参数入栈的方式是从左到右还是从右到左?
(2)栈的维护方式
函数出栈的工作是由调用方来完成还是被调用方完成?

一般来说,c语言默认使用 cdecl 惯例,_如果没有手动指定,编译器会自动加上(windows默认stdcall)

int _cdecl func(){};

调用前后的两个函数,调用惯例需要一样。

3:指针

32位下,所有指针都是4个字节,里面存的是内存地址(64位操作系统是8个字节)
指针步长: 指针变量+1时,要向后跳多少字节,不同类型的指针就表现在指针步长不同。
解引用所取字节数 + 指针步长 = 指针类型

4:free使用后要记得把指针置空

free使用后要记得把指针置空

char a[] = "192.168.0.1";
char buf[1024] = {0};
strcpy(buf,a);
free(buf);
// buf = NULL
cout << buf << endl;	//缺少buf = NULL,输出还是 192.168.0.1

三:一些c中常用函数

1:计算结构体中成员变量偏移量 offsetof
#include <stddef.h>
cout << offsetof(struct Person, b) << endl;
2:在堆区分配内存 malloc
char* s = malloc(sizeof(char) * 100);
3:初始化 memset
memset(s, 0, 100);	//将s指向内存的前100个字节设置为0
4:复制 strcpy

大长度字符串复制给小长度字符串小心内存溢出

strcpy(s,"Hello world")
5:使用占位符拼接字符串 sprintf (以字符串作为输出)
char buf[1024] = {0};
sprintf(buf,"hello%d world",1);
cout << buf << endl;

//输出:hello1 world
6:calloc 函数——分配连续的一段空间,并初始化
calloc(10,sizeof(int))
7:sizeof 函数
int a = 0;
cout << sizeof(a) << endl;
cout << sizeof(int) << endl;

这里两个都是输出 4

8:重新分配空间 realloc

如果原内存地址后续连续空间不够,就会重新分配一块内存,并将原内存内容复制过来

realloc(p, 10000)	//p指针指向的内存
9:sscanf 输出匹配字符串

这里 %*d 是忽略整型的意思

int main(){
    char a[] = "1234abcd";
    char buf[1024] = {0};
    sscanf(a,"%*d%s",buf);
    cout << buf << endl;
}
int main(){
    char a[] = "192.168.0.1";
    int num1 = 0, num2 = 0, num3 = 0, num4 = 0;
    sscanf(a,"%d.%d.%d.%d",&num1,&num2,&num3,&num4);
    printf("%d,%d,%d,%d",num1,num2,num3,num4);
//输出:192,168,0,1
}

如果是 %*s则是忽略字符串,匹配直到空格或者制表符之类的才结束。
占位符的书写和正则表达式很像

%[a-z]	表示a到z的所有字符
%[aBc]	表示aBc中的某个字符
%[^a]	表示除a之外的字符
%[^a-z]	表示除a-z之外的所有字符

四:数组

1:数组在sizeof的时候,不是指针
int a[] = {1,2,3,4}
cout << sizeof(a) << endl; //16
2:数组名是一个常量指针
char a[] = "192.168.0.1";
a = NULL;	//报错,a是常量指针
3:给函数传参的时候,数组的长度是不知道的,所以在传参的时候,一定要传长度
void func(int* a,int len){};
4:使用typedf定义数组指针类型

typedef作用于数组

typedef int(ARRAY)[5]
ARRAY a;
ARRAY* p = &a;	//p是一个地址,是指向整个数组的指针,因此步长就是数组的大小

定义数组指针(是一个一级指针,但是步长是数组的长度)

typedef int(ARRAY*)[5]
int a[5] = {0};

//下面两句是等价的
ARRAY p = &a; 
int(*p2)[5] = &a;
5:二维数组

二维数组也是一个二级指针,步长是指针大小(下面指向的类型是a[3])

int arr[3][3] = {1,2,3,4,5,6,7,8,9};
int(*p)[3] = arr;

五:结构体

1:结构体的一种特殊写法

简单的结构体的写法和 typedef 先不说,来个奇葩的

//定义结构体的同时,定义了一个结构体变量
struct Person{
	char name[20];
	int age;
}person = {"ttt",28};
2:结构体内存对齐

原因 :方便计算机寻址,计算机一个字节一个字节读数据,速度太慢,所以会一块一块的读,为了避免读取的数据被分割(前后两次读读了同一个变量的不同部分),就有了字节对齐。
对齐规则:

  1. 第一个数据成员应该放在 offset 为0的地方
  2. 之后的数据成员应该放在 min(当前成员大小,#pragma pack(n))
    整数倍的地方,比如当前成员是int,默认对齐模式是8,则应该放在4的整数倍的位置。
  3. 最后结构体所占的字节数,应该是最大成员的整数倍,比如最大元素是 double,那么结构体所占字节数应该是8的整数倍。
    使用 pragma pack(n) 设置默认对齐模式为n

六:函数指针

函数名是函数的入口地址或者说本质上是一个存了函数地址的指针
定义函数指针:

void func(int a,char b){
    cout << "hello world" << endl;
}
int main(){
    typedef void(FUNC)(int,char);	//定义函数指针类型
    //typedef void(*FUNC)(int,char);	也可以直接定义为指针类型
    FUNC* p = func;	//生成函数指针
    p(1,'a');
    //(*p)(1,'a');	//解引用也可以,上面写法可以理解为编译器加上了 *

	//直接定义一个函数指针
	void(*p2)(int,char) =func;
}

七:其他

1:c++中使用c语言
//防止头文件重复包含
#pragma once

//在c++中能够使用c语言函数(c++中包含c语言的头文件会报错,编译的过程不同,比如对函数的修饰等)
#ifdef __cplusplus
	extern "C"{
#endif

#ifdef __cplusplus
}
#endif
2:预处理的基本概念

c语言对源程序处理的四个大步骤:预处理、编译、汇编、链接
预处理:# 开头的就是预处理指令

普通宏定义

#define PI 3.14	//宏定义,作用域是当前文件(就算定义在函数里面,作用域也是整个文件)
#undef PI	//销毁宏定义,销毁之前可以使用,销毁之后就不能使用了
#define SUM(x,y) (x+y)	//宏函数

条件编译

//条件编译 1
#ifdef FLAG
	满足条件执行的代码
#else
	不满足条件执行的代码
#endif

//条件编译 2
#ifndef FLAG
#else
#endif

//条件编译3
#if 表达式
#else 
#endif

打印错误和位置

printf("%s的%d行出错!",__FILE__,__LINE__)
3: sscanf 和 sprintf

这两个可用于字符串和数字之间的相互转换,比c++的api要方便很多。
两个函数的第一个参数都是字符串,第二个参数是模式串,第三个参数 sprintf是数字,sccanf是数字变量地址。

float val = 0.12312312;
    char val_c[100];
    //数字转字符串
    sprintf(val_c, "%.2f", val);
    cout << val_c << endl;
    
    //字符串转数字
    sscanf(val_c, "%f", &val);
    cout << val << endl;
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现命令行的 Tab 补全功能,您可以使用以下 C 语言源码作为基础: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> // 命令列表 char* commandList[] = { "help", "cd", "ls", "mkdir", "rm", "cp", }; // 获取匹配的命令 char* getMatchingCommand(char* input) { int i; int size = sizeof(commandList) / sizeof(commandList[0]); for (i = 0; i < size; i++) { if (strncmp(input, commandList[i], strlen(input)) == 0) { return commandList[i]; } } return NULL; } // 补全命令 void autoCompleteCommand(char* input) { int i; int size = sizeof(commandList) / sizeof(commandList[0]); for (i = 0; i < size; i++) { if (strncmp(input, commandList[i], strlen(input)) == 0) { printf("%s ", commandList[i]); } } } int main() { char input[100]; printf("Enter command: "); fgets(input, sizeof(input), stdin); input[strlen(input) - 1] = '\0'; // 去除换行符 char* matchingCommand = getMatchingCommand(input); if (matchingCommand != NULL) { printf("Matching command found: %s\n", matchingCommand); } else { printf("No matching command found.\n"); autoCompleteCommand(input); } return 0; } ``` 在这个示例中,我们添加了一个 `autoCompleteCommand` 函数,用于补全命令。该函数会遍历命令列表,并将与输入字符串匹配的命令输出到控制台。 在 `main` 函数中,如果没有找到匹配的命令,我们调用 `autoCompleteCommand` 函数来进行命令补全。它会根据用户输入的部分命令,在命令列表中查找并输出可能的补全选项。 这只是一个简单的示例,实际的命令行补全功能可能更复杂,需要考虑更多的匹配规则和上下文。您可以根据自己的需求进行扩展和优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值