C语言基本知识

基础

第一个函数

argc代表参数个数argument count。argv代表参数value,第一个为放的是文件名,后面是传入的参数。

编译过程

  • 预处gcc -E hello.c -o hello.i
  • 编译gcc -S hello.c(.i) -o hello.s
  • 汇编gcc -c hello.c -o hello.o
  • 连接gcc hello.o -o hello
  • 一步到位:gcc hello.c -o hello

生成文件

存储类

auto:用完即丢。

static:本文件的全局变量。

extern:只声明,不定义,引用外部变量。

register:放在寄存器而不是内存。

c 复制代码
//auto
{
   auto int month;	//   等于int mount;
}

//register
{
   register int  miles;
}

//static
static int count=10;        /* 全局变量 - static 是默认的 */
int main(){
    
}

extern实例

c 复制代码
//文件add.cpp
extern int x;
extern int y;
int add(){
	return x+y;
}

//文件test.cpp
#include <stdio.h>
int x = 2;
int y = 5;
extern int add();	//不用extern也可以
int main(){
    int z = add();
    printf("%d\n",z);
}

//cmd下
$ gcc add.cpp test.cpp -o sum
//返回结果
$ sum		
    
    
//区别extern
extern int a;     // 声明一个全局变量 a
extern int a =0;  // 定义一个全局变量 a 并给初值。一旦给予赋值,一定是定义,定义才会分配存储空间

全局变量static保存在内存的全局存储区堆中,占用静态的存储单元;

局部变量auto保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。

常量

整数、浮点数、字符、字符串(字符串常量以'\0'或者null结尾)。

定义常量define或者const修饰:define只是进行文本替换,const用于只读变量,会为其分配内存(不会为常量分配内存),还可以捕获一些潜在的错误。所以建议使用const

c 复制代码
3.14159       /* 合法的 */
314159E-5L    /* 合法的 */
    
//浮点数==整数+小数。科学计数法==小数+指数
510E          /* 非法的:不完整的指数 */
210f          /* 非法的:没有小数或指数 */
.e55          /* 非法的:缺少整数或分数 */
 
//s1和s2相同
char *s1 = "hello, dear";
char *s2 = "hello, " "d" "ear";

数据类型

基本,指针,构造(数组、结构、枚举、共用),空。

整形数据根据编译环境的不同,所取范围不同。

浮点数

24位:符号+底数。8位:指数(偏移值为+127)。

c 复制代码
##include<cfloat>头文件,知道原理
printf("%e\n",FLT_MIN) ;	//1.175494e-038
printf("%e\n",FLT_MAX) ;	//3.402823e+038  

//二者均为常量9
int a = '\11';	
int b = 011;	

运算符

算数、逻辑、位、关系、赋值。杂运算符:sizeof()、&(取地址)、*(指向一个变量)、?:。

优先级:后缀((),->,++/--),单目(!/sizeof/++/--),算数,移位,大小关系,相等关系。位运算,逻辑运算。三元,赋值,逗号。

格式化输出

格式字符,转义字符,普通字符。格式修饰符。g和G?l和L?h?%.ns表示?%-m.nf表示什么意思?

枚举

枚举类型是被当做 int 类型来处理的,所以按照 C 语言规范是没有办法遍历枚举类型的。不过在一些特殊的情况下,枚举类型必须连续是可以实现有条件的遍历。

c 复制代码
//不可以遍历
enum
{
    ENUM_0, ENUM_10 = 10, ENUM_11
};

//可以遍历。默认MON是0,但是这里赋值为1
enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
}day;
int main()
{
    // 遍历枚举元素
    for (day = MON; day <= SUN; day++) {
        printf("枚举元素:%d \n", day);
    }
}

指针

指针的含义。&*

函数指针

函数指针:一个指向函数的指针int (*p)(int,int) = fun

c 复制代码
//函数指针
int max(int a,int b){ return a>b?a:b; }
int main(){
	int (*p)(int,int) = &max;//&可以不写 
	p(1,2);		//等价max(1,2) 
	return 0;
}

回调函数

populate_array() 将调用 10 次回调函数,并将回调函数的返回值赋值给数组。

c 复制代码
void populate_array(int *array,size_t length,int (*getValue)()){
	for(int i=0 ; i<length ; i++){
		*(array+i) = getValue();	//这里
	}
}

int getRandom(){ return rand(); }	//回调函数

int main(){
	int array[10];
     //getRandom不能加括号否则无法编译,因为加上括号之后相当于传入此参数时传入了 int,而不是函数指针
	populate_array(array,10,getRandom);
	//此时for输出array
	return 0;
}

//size_t 是一种数据类型,近似于无符号整型,但容量范围一般大于 int 和 unsigned。这里使用 size_t 是为了保证 arraysize 变量能够有足够大的容量来储存可能大的数组。

字符串

\0 是用于标记字符串的结束

c 复制代码
#include <string.h>
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
char site[] = "RUNOOB";
char site[6] = {'R', 'U', 'N', 'O', 'O', 'B'};//如果当作字符串输出会乱


strcpy(s1, s2);	//复制字符串 s2 到字符串 s1。
strcat(s1, s2); //连接字符串 s2 到字符串 s1 的末尾。catenate 
strlen(s1);		//返回字符串 s1 的长度。
strcmp(s1, s2);	//如果 s1 和 s2 是相同的则返回0如果 s1<s2 则返回小于0;如果 s1>s2 则返回大于 0。
strchr(s1, ch);	//返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
strstr(s1, s2);	//返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
strupr(s1);
strlwr(s1);

结构体

定义结构体:

c 复制代码
#1
//声明了结构体变量s1
struct{
    int a;
    char b;
    double c;
} s1;


#2
struct SIMPLE{
    int a;
    char b;
    double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;


#3
//也可以用typedef创建新类型
typedef struct Simple2{
    int a;
    char b;
    double c;
};
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

struct Books{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};

实例:

c 复制代码
struct Person {
    char name[20];
    int age;
    float height;
};

int main() {
    struct Person person;
    printf("结构体 Person 大小为: %zu 字节\n", sizeof(person));
    return 0;
}

使用 sizeof 运算符来获取 person 结构体的大小,结构体的大小可能会受到编译器的优化和对齐规则的影响。了解结构体的内存布局和对齐方式,可以使用 offsetof 宏和 _attribute_((packed)) 属性等进一步控制和查询结构体的大小和对齐方式。

共用体

定义

c 复制代码
union Data
{
   int i;
   float f;
   char  str[20];
} data1;

union Data data2; 	//sizeof(data2) = 20,会采用共用体中最大的元素作为对象的大小 

如果其中对象的一个元素被赋值,其他的元素都会受到损害。共用体用于多个元素只会用其中的一种情况,比如传递信息的包(只会传递一种包)。

位域

位域bit-field是结构体中特殊的成员,指定成员占用多少bit,位域的宽度不能超过其数据类型的大小。

c 复制代码
/* 定义简单的结构 */
struct
{
  unsigned int a;
  unsigned int b;
} status1;		//sizeof(status1)=8
 
/* 定义位域结构 */
struct
{
  unsigned int a : 1;	//指定a占用1bit,也就是a只能为0或者1
  unsigned int b : 1;	//指定b占用1bit
} status2;		//sizeof(status2)=4


//一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。也可以有意使某位域从下一单元开始。
struct
{
  char a : 2;
  char b : 3;
  char c : 3;
} status3;		//sizeof(status3)=1
 

//位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的。
struct k{
    int a:1;
    int  :2;    /* 该 2 位不能使用 */
    int b:3;
    int c:2;
};

结构体内存分配原则

原则一:元素根据自己的整数倍进行存储。

原则二:struct的大小为元素(最占空间)的倍数。

typedef

自定义类型。

c 复制代码
//C语言中没有byte的数据类型
typedef unsigned char byte;
byte a = 1;



typedef struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} Book;
Book book;

typedef区别#define:(typedef定义类型,define文本替换)

  • typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
  • typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
c 复制代码
//#define可以使用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做。
#define INTERGE int;
unsigned INTERGE n;  //没问题
typedef int INTERGE;
unsigned INTERGE n;  //错误,不能在 INTERGE 前面添加 unsigned


#define PTR_INT int *
PTR_INT p1, p2;        //p1、p2 类型不相同,宏展开后变为int *p1, p2;
typedef int * PTR_INT
PTR_INT p1, p2;        //p1、p2 类型相同,它们都是指向 int 类型的指针。

输入 & 输出

int scanf(const char *format, ...)int printf(const char *format, ...)

int getchar(void)int putchar(int c)

**char *gets(char s) int puts(const char s)

C 语言把所有的设备都当作文件。所以设备(比如显示器)被处理的方式与文件相同。以下三个文件会在程序执行时自动打开,以便访问键盘和屏幕。

标准文件 文件指针 设备
标准输入 stdin 键盘
标准输出 stdout 屏幕
标准错误 stderr 您的屏幕
c 复制代码
#1:
int getchar(void); //从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。
int putchar(int c); // 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。

int c = getchar();
putchar(c);



#2:
char *gets(char *s); //函数从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF。读取的换行符被转换为null值,做为字符数组的最后一个字符,来结束字符串。gets函数由于没有指定输入字符大小,如果很大会越界。
int puts(const char *s); //函数把字符串 s 和一个尾随的换行符写入到 stdout。

char str[100];
gets(str);
puts(str);


#3:
scanf() 和 printf() 函数

    
    
#4:
fgets函数原型:char *fgets(char *s, int n, FILE *stream);我们平时可以这么使用:fgets(str, sizeof(str), stdin);取代gets(),原因gets()易越界

char str[5];
fgets(str, sizeof(str), stdin);
fputs(str,stdout);

文件读写

FILE *fopen( const char *filename, const char *mode );

r 打开一个已有的文本文件,允许读取文件。
w 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。
a 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。
r+ 打开一个文本文件,允许读写文件。
w+ 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。
a+ 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。

如果是二进制"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"。

函数介绍:(错误返回EOF)

c 复制代码
int fputc( int c, FILE *fp );
int fputs( const char *s, FILE *fp );
int fprintf(FILE *fp,const char *format, ...) 
int fgetc( FILE * fp );
char *fgets( char *buf, int n, FILE *fp );	//读取n-1,因为最后要有'\0'。遇到换行即停
int fscanf(FILE *fp, const char *format, ...) 	//遇到空格换行即停。
    

实验

c 复制代码
#include <stdio.h>
 
int main()
{
   FILE *fp = NULL;
 
   fp = fopen("C:\\tmp\\test.txt", "w+");
   fprintf(fp, "This is testing for fprintf...\n");
   fputs("This is testing for fputs...\n", fp);
   fclose(fp);
}


#include <stdio.h>
 
int main()
{
   FILE *fp = NULL;
   char buff[255];
 
   fp = fopen("C:\\tmp\\test.txt", "r");
   fscanf(fp, "%s", buff);
   printf("1: %s\n", buff );
 
   fgets(buff, 255, (FILE*)fp);
   printf("2: %s\n", buff );
   
   fgets(buff, 255, (FILE*)fp);
   printf("3: %s\n", buff );
   fclose(fp);
 
}
//输出
1: This
2: is testing for fprintf...

3: This is testing for fputs...

实验

int fseek(FILE *stream, long offset, int whence);文件中光标偏移

whence有

c 复制代码
#include <stdio.h>
int main(){   
    FILE *fp = NULL;
    fp = fopen("test.txt", "r+");  // 确保 test.txt 文件已创建
    fprintf(fp, "This is testing for fprintf...\n");   
    fseek(fp, 10, SEEK_SET);
    if (fputc(65,fp) == EOF) {
        printf("fputc fail");   
    }   
    fclose(fp);
}

#打开test.txt
This is teAting for fprintf...

预处理器

C Preprocessor简写为CPP

指令 描述
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if......#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中。pragma"注释"

实例

c 复制代码
//取消定义,并重新定义
#undef  FILE_SIZE
#define FILE_SIZE 42

//ifdef---endif
#ifndef MESSAGE
   #define MESSAGE "You wish!"
#endif

内置的宏定义

c 复制代码
#include <stdio.h>
 
main()
{
   printf("File :%s\n", __FILE__ );		
   printf("Date :%s\n", __DATE__ );
   printf("Time :%s\n", __TIME__ );
   printf("Line :%d\n", __LINE__ );		//__LINE__当前行号
   printf("ANSI :%d\n", __STDC__ );		//__STDC__,当编译器以ANSI编译器时,为1
 
}

实例

c 复制代码
//'\'表示宏延续运算符,'#'字符串常量化运算符。宏定义中printf如果被大括号括,则需要在末尾有分号
#define  message_for(a, b)  \
    printf(#a " and " #b ": We love you!\n")
int main(void){
   message_for(Carole, Debra);
   return 0;
}
//输出
Carole and Debra: We love you!
    

//'##'标记粘贴运算符
#define tokenpaster(n) printf ("token" #n " = %d", token##n) 
int main(void){
   int token34 = 40;
   tokenpaster(34);
   return 0;
}

头文件

include <> 和 include ""区别。

有条件引用

c 复制代码
#if SYSTEM_1
   # include "system_1.h"
#elif SYSTEM_2
   # include "system_2.h"
#elif SYSTEM_3
   ...
#endif
  
//需要指定在不同的操作系统上使用的配置参数。
#if SYSTEM_1
   # include "system_1.h"
#elif SYSTEM_2
   # include "system_2.h"
#elif SYSTEM_3
   ...
#endif

//有条件引用
#define SYSTEM_H "system_1.h"
...
#include SYSTEM_H

//global.h包含所有头文件,以后直接引用global.h即可
#ifndef _GLOBAL_H
	#define _GLOBAL_H
#endif 
#include <fstream>
#include <iostream>
#include <math.h>
    

强制类型转换

char/short → int → unsigned → long → double ← float

错误处理

errno、perror()、strerror()、可以直接使用 stderr 文件流

c 复制代码
#include <stdio.h>
#include <errno.h>
#include <string.h>

extern int errno;

int main(){
	FILE *fp = fopen("unexist.ext","rb");
	if(fp == NULL){
		perror("打开错误\n");
		fprintf(stderr,"---%s",strerror(errno));
	}else{
		fclose(fp);
	}
	
	return 0;
} 
//输出
打开错误
: No such file or directory
---No such file or directory

实例

c 复制代码
//退出程序时返回状态。EXIT_FAILURE和EXIT_SUCCESS是宏定义,分别是-1和0
#include <stdio.h>
#include <stdlib.h>
 
main()
{
   int dividend = 20;
   int divisor = 5;
   int quotient;
 
   if( divisor == 0){
      fprintf(stderr, "除数为 0 退出运行...\n");
      exit(EXIT_FAILURE);
   }
   quotient = dividend / divisor;
   fprintf(stderr, "quotient 变量的值为: %d\n", quotient );
 
   exit(EXIT_SUCCESS);
}

可变参数

实例

c 复制代码
va_list ;	//stdarg.h中的类型
va_start(ap, last_arg)	//ap为va_list,last_arg表示最后一个可变参数。ap指向可变参数的第一个元素
va_arg(ap, type)	//可变参数的下一个参数
va_end(ap)			//结束,ap置空
    
#include <stdio.h>
#include <stdarg.h>
float fun_average(int num,...){
	va_list valist;
	va_start(valist,num);
	float sum = 0;
	for(int i=0; i<num ; i++){
		sum += va_arg(valist,int);
	}
	va_end(valist);
	return sum/num;
} 
int main(){	
	printf("%f\n",fun_average(3,5,6,7));
	printf("%f\n",fun_average(4,5,6,7,8));
	return 0;
}

内存管理

函数均在stdlib.h文件中

1 void *calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是 0。c表示clear
2 void free(void *address); 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
3 void *malloc(int num); 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
4 void *realloc(void *address, int newsize); 该函数重新分配内存,把内存扩展到 newsize

实例

c 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(){
	char *p;
	p = (char*)malloc(5*sizeof(char));
	if(p == NULL){
		fprintf(stderr,"---1stderr");
	}else{
		strcpy(p,"1234");
	}
	printf("%s\n",p);
	p = (char*)realloc(p,10*sizeof(char));
	if(p==NULL){
		fprintf(stderr,"---2stderr");
	}else{
		strcat(p,"12345");
	}
	printf("%s\n",p);
	free(p);
	return 0;	
	
}

命令行参数

c 复制代码
#include <stdio.h>

int main( int argc, char *argv[] )  
{
   if( argc == 2 )
   {
      printf("The argument supplied is %s\n", argv[1]);
   }
   else if( argc > 2 )
   {
      printf("Too many arguments supplied.\n");
   }
   else
   {
      printf("One argument expected.\n");
   }
}

//执行。argv[0]存储程序的名字,argv[1]存储第一个输入的参数
$./a.out testing
The argument supplied is testing

$./a.out testing1 testing2
Too many arguments supplied.

$./a.out
One argument expected

$./a.out "testing1 testing2"

Progranm name ./a.out
The argument supplied is testing1 testing2

排序

  • 冒泡排序:略
  • 选择排序:略
  • 插入排序:略
  • 希尔排序:

有待解决

例子

基础

C 复制代码
#两整数相除任然是整数。
printf("%f",(float)(10/3));		//3.000000

int *ptr;		//ptr:整型指针变量

//后缀可以是大写,也可以是小写,U 和 L 的顺序任意。
0xFeeL      /* 合法的 */
0x4b       /* 十六进制 */
30u        /* 无符号整数 */
30l        /* 长整数 */
30ul       /* 无符号长整数 */
    
#'\101'等价于0101
'\101', '\x41', 'A'	//三者等价
    

变量修饰符

c 复制代码
//用循环函数检测使用时间
time_t start,end;
time(&start);
		fori;
time(&end);
printf("%ld",start-end);

函数

c 复制代码
#add.c
#include <stdio.h>
/*外部变量声明*/
extern int x ;
extern int y ;
int add()
{
    return x+y;
}


#test.c
#include <stdio.h>
/*定义两个全局变量*/
int x=1;
int y=2;
int add();
int main()
{
    printf(" %d\n",add());
    return 0;
}

拓展

编码

  1. ascii编码规则?常用字符?
  2. ANSI编码即扩展ascii编码。GB2312(94*94)三种编码方式、GBK用剩余的空间再进行编码、Big-5、JIS
  3. utf-8编码规则?

格式化输出

  1. 整形输出:%ld %o %x %u %5d %05d %-5d
  2. 小数输出:%20.15f %-20.15f %e %g
  3. 字符输出:%5c
  4. 指针输出:%p
  5. 格式修饰符:l、 L、h、m.n、-

visual stdio(vs)和vs code

两者都是microsoft下

vs:通俗的说是一个编译器。不可跨平台,是IDE,可以编译很多高级语言。

vs code:通俗的说是一个编辑器。可跨平台,超级文本编辑器。

vc(visual C++):编译器,只用于C/C++。

问题

  1. 静态(static)和外部(extern)类型的数组元素初始化元素为0,自动(auto)类型的数组的元素初始化值不确定。

/三者等价

变量修饰符

```c
//用循环函数检测使用时间
time_t start,end;
time(&start);
		fori;
time(&end);
printf("%ld",start-end);

函数

c 复制代码
#add.c
#include <stdio.h>
/*外部变量声明*/
extern int x ;
extern int y ;
int add()
{
    return x+y;
}


#test.c
#include <stdio.h>
/*定义两个全局变量*/
int x=1;
int y=2;
int add();
int main()
{
    printf(" %d\n",add());
    return 0;
}

拓展

编码

  1. ascii编码规则?常用字符?
  2. ANSI编码即扩展ascii编码。GB2312(94*94)三种编码方式、GBK用剩余的空间再进行编码、Big-5、JIS
  3. utf-8编码规则?

格式化输出

  1. 整形输出:%ld %o %x %u %5d %05d %-5d
  2. 小数输出:%20.15f %-20.15f %e %g
  3. 字符输出:%5c
  4. 指针输出:%p
  5. 格式修饰符:l、 L、h、m.n、-

visual stdio(vs)和vs code

两者都是microsoft下

vs:通俗的说是一个编译器。不可跨平台,是IDE,可以编译很多高级语言。

vs code:通俗的说是一个编辑器。可跨平台,超级文本编辑器。

vc(visual C++):编译器,只用于C/C++。

问题

  1. 静态(static)和外部(extern)类型的数组元素初始化元素为0,自动(auto)类型的数组的元素初始化值不确定。

相关推荐
汤米粥1 分钟前
小皮PHP连接数据库提示could not find driver
开发语言·php
冰淇淋烤布蕾4 分钟前
EasyExcel使用
java·开发语言·excel
拾荒的小海螺10 分钟前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
马剑威(威哥爱编程)35 分钟前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
娃娃丢没有坏心思37 分钟前
C++20 概念与约束(2)—— 初识概念与约束
c语言·c++·现代c++
白-胖-子1 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-统计数字
开发语言·c++·算法·蓝桥杯·等考·13级
好睡凯1 小时前
c++写一个死锁并且自己解锁
开发语言·c++·算法
java—大象1 小时前
基于java+springboot+layui的流浪动物交流信息平台设计实现
java·开发语言·spring boot·layui·课程设计
yyqzjw1 小时前
【qt】控件篇(Enable|geometry)
开发语言·qt
csdn_kike1 小时前
QT Unknown module(s) in QT 以及maintenance tool的更详细用法(qt6.6.0)
开发语言·qt