c语言_随笔
C语言技能树 https://bbs.csdn.net/skill/c
c语言开发环境(VS Code)
下载编译器:https://sourceforge.net/projects/mingw-w64/files/ 往下稍微翻一下,选最新版本中的x86_64-posix-seh
。最好不要用 Download Latest Version,这个是在线安装包
看好bin文件夹的完整路径,我是C:\mingw64\bin,把它加到环境变量中的PATH里去。Debian系Linux用sudo apt update; sudo apt install build-essential
即可。
输gcc -v
可以显示出gcc的版本。如果显示出来的版本与你刚下的不同/更老,说明Path里原本有老版本的编译器,可能是安装其它IDE时装上的。则需要去掉Path里原来的那一个gcc的路径。
安装扩展(extension)
- C/C++:又名 cpptools,提供Debug和Format功能
- Code Runner:右键即可编译运行单文件,很方便;但无法Dubug
注意:如果程序里有scanf()等请求键盘输入数据的函数,此时无法从键盘输入数据,并且程序无法结束需要关闭重启vscode才能重新执行
解决办法是依次打开:文件>首选项>设置>用户设置>拓展>Run Code Configuration
找到 Run In Terminal 打上勾 这样运行的程序就会运行在vscode的集成控制台上
编译运行
f5
c程序编译多文件(通过tasks.json)
launch.json
{ // 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "gcc.exe - 生成和调试活动文件", "type": "cppdbg", "request": "launch", "program": "${fileDirname}\\${fileBasenameNoExtension}.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "miDebuggerPath": "C:\\mingw64\\bin\\gdb.exe", "setupCommands": [ { "description": "为 gdb 启用整齐打印", "text": "-enable-pretty-printing", "ignoreFailures": true } ], "preLaunchTask": "gcc.exe build active file" } ] }
tasks.json
{ "version": "2.0.0", "tasks": [ { "type": "shell", "label": "gcc.exe build active file", "command": "C:\\mingw64\\bin\\gcc.exe", "args": [ "-g", "${file}", "${fileDirname}\\package\\hello.c", "-o", "${fileDirname}\\${fileBasenameNoExtension}.exe" ], "options": { "cwd": "C:\\mingw64\\bin" }, "problemMatcher": [ "$gcc" ], "group": "build" } ] }
Windows下C语言的Socket编程例子(TCP和UDP)
重要提醒:如使用了winsock2.h gcc编译器必须增加 -lws2_32 指令
server.c
#include <stdio.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") int main(int argc, char *argv[]) { //初始化WSA WORD sockVersion = MAKEWORD(2, 2); WSADATA wsaData; if (WSAStartup(sockVersion, &wsaData) != 0) { return 0; } //创建套接字 SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (slisten == INVALID_SOCKET) { printf("socket error !"); return 0; } //绑定IP和端口 struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(8888); sin.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { printf("bind error !"); } //开始监听 if (listen(slisten, 5) == SOCKET_ERROR) { printf("listen error !"); return 0; } //循环接收数据 SOCKET sClient; struct sockaddr_in remoteAddr; int nAddrlen = sizeof(remoteAddr); char revData[255]; while (1) { printf("等待连接...\n"); sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen); if (sClient == INVALID_SOCKET) { printf("accept error !"); continue; } printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); //接收数据 int ret = recv(sClient, revData, 255, 0); if (ret > 0) { revData[ret] = 0x00; printf(revData); } //发送数据 char *sendData = "你好,TCP客户端!\n"; send(sClient, sendData, strlen(sendData), 0); closesocket(sClient); } closesocket(slisten); WSACleanup(); return 0; }
client.c
#include <stdio.h> #include <winsock2.h> #include <STDIO.H> #pragma comment(lib, "ws2_32.lib") int main(int argc, char *argv[]) { WORD sockVersion = MAKEWORD(2, 2); WSADATA data; if (WSAStartup(sockVersion, &data) != 0) { return 0; } SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sclient == INVALID_SOCKET) { printf("invalid socket !"); return 0; } struct sockaddr_in serAddr; serAddr.sin_family = AF_INET; serAddr.sin_port = htons(8888); serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if (connect(sclient, (struct sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) { printf("connect error !"); closesocket(sclient); return 0; } char *sendData = "你好,TCP服务端,我是客户端!\n"; send(sclient, sendData, strlen(sendData), 0); char recData[255]; int ret = recv(sclient, recData, 255, 0); if (ret > 0) { recData[ret] = 0x00; printf(recData); } closesocket(sclient); WSACleanup(); return 0; }
------------------------------------------------------------------------------------------------分割线------------------------------------------------------------------------------------------------
LIB
nanomsg是一个socket library,提供了几种常见的通信模式
void f(void){ static int i;//static local variable ... }
因为局部变量的i已经声明为static,所以在程序执行期间它所占据的内存单元是不变的。在f返回时,变量i不会丢失其值。
静态局部变量始终有块作用域,所以它对其它函数是不可见的。概括来说,静态变量是对其它函数隐藏数据的地方,但是它会为将来同一个函数的在调用保留这些数据。
const保护参数
void f(const int *p){ *p = 0; //wrong }
指针作为返回值
int *max(int *a, int *b){ if(*a>*b) return a; else return b; } int *p,i,j; ... p = max(&i,&j);
指针的算数运算
只有3种方式
指针加上整数;
指针减去整数;
两个指针相减;
指向符合常量的指针
int *p = (int []){3,0,3,4,1}; //对比 int a[] = {3,0,3,4,1}; int *p = &a[0];
指针用于数组处理
#define N 10 ... int a[N],sum,*p; ... sum = 0; for(p = &a[0]; p < &a[N]; p++) sum += *p; //简化 for(p = a; p < a + n; p++) sum += *p;
用数组名作为指针
int a[10]; *a = 7;//store 7 in a[0] *(a+1) = 12;//store 12 in a[1] //通常情况下,a+i 等同于&a[i]
预处理指令
#define //宏定义 #undef //删除一个宏定义 #include //文件包含 #if #ifdef #ifndef #elif #else #endif //条件编译
带参数宏
#define MAX(x,y) ((x)>(y)?(x):(y)) #define IS_EVEN(n) ((n)%2==0) //检测字符c是否在'a'与'z'之间,在:返回对应的大写,不在:原值返回 #define TOUPPER(c) ('a'<=(c)&&(c)<='z'?(c)-'a'+'A':(c)) //#运算符将宏的一个参数转换为字符串字面量 #define PRINT_INT(n) printf(#n " = %d\n",n) //##运算符可以将两个记号(如标识符)“粘合”在一起,成为一个记号 #define MK_ID(n) i##n
参数可变宏
#define TEST(condition,....) ((condition)?\ printf("Passed test:%s\n",#condition):\ printf(__VA_ARGS__))
预定义宏
__LINE__ 被编译的文件中的行号
__FILE__ 被编译的文件名
__DATE__ 编译的日期(格式“Mmm dd yyyy”)
__TIME__ 编译的时间(hh:mm:ss)
__STDC__ 编译器符合c标准(c89 c99)值为1
...等
共享变量的声明
int i;//declares i and defines it as well
extern int i; //declares i without defining it
extern 告诉编译器,变量i是在程序中其它位置定义的(很可能是在不同的源文件中),因此不需要为i分配空间。
结构标记的声明
struct part{ int number; char name[NAME_LEN+1]; int on_hand; }; struct part part1,part2;
结构类型的定义
typedef struct{ int number; char name[NAME_LEN+1]; int on_hand; } Part; Part part1,part2;
联合
//编译器只为联合中最大的成员分配足够的内存空间。 union{ int i; double d; } u;
枚举
//与结构和联合一样,两种方法命名枚举。 enum suit{CLUBS,DIAMONDS,HEARTS,SPADES}; enum suit s1,s2; typedef enum {CLUBS,DIAMONDS,HEARTS,SPADES} Suit; Suit s1,s2;
内存分配函数
malloc 分配内存块,但是不对内存块进行初始化。
calloc 分配内存快,并且对内存块进行清零。
realloc 调整先前分配内存快大小。
//为字符串分配内存,实际参数是n+1而不是n,给空字符留了空间 p = (char *)malloc(n+1); //对上述数组初始化的一种方法是调用strcpy函数 strcpy(p,"abc");
//使用malloc函数为数组分配储存空间 int *a; a = malloc(n * sizeof(int)); for(i = 0; i < n; i++) a[i] = 0;
//为nmemb个元素的数组分配内存空间,其中每个元素的长度是size个字节。 void *calloc(size_t nmemb, size_t size); //保证所有整数初始均为零 a = calloc(n, sizeof(int)); //p指向一个结构,此结构的成员x和y都被设为零 struct point {int x, y;} *p; p = calloc(1, sizeof(struct point));
//prt指向分配好的内存块,size表示内存块的新尺寸 void *realloc(void *ptr, size_t size);
free函数
//“悬空指针” char *p = malloc(4); ... free(p); ... strcpy(p,"abc");//wrong
函数指针
//integrate函数求函数f在a点和b点之间的积分 double integrate(double (*f)(double),double a,double b); double integrate(double f(double),double a,double b); //sin是函数 result = integrate(sin,0.0,PI/2);
qsort函数 给任意数组排序的通用函数
/** *base 数组中第一个元素 *nmemb 要排序元素的数量 *size 每个数组元素的大小(字节) *compar 比较函数的指针 */ void qsort(void *base, size_t nmemb, size_t size, int (*compar) (const void *, const void *)); qsort(inventory,num_parts,sizeof(struct part),compare_parts); int compare_parts(const void *p, const void *q){ const struct part *p1 = p; const struct part *q1 = q; if(p1->number < q1->number) return -1; else if(p1->number == q1->number) return 0; else return 1; } //简写 int compare_parts(const void *p, const void *q){ if(((struct part *)p)->number < ((struct part *)q)->number) return -1; else if(((struct part *)p)->number == ((struct part *)q)->number) return 0; else return 1; }
函数指针的其它用途
可以把函数指针存储在变量中,或者用作数组的元素,再或者用作结构或联合的成员,甚至可以编写返回函数指针的函数。
//pf可以指向任何带有int型形式参数并且返回void型值的函数。 void (*pf)(int); //如果f是这样的函数,pf指向f pf = f; //注意,在f的前面没有取地址符号(&)。一旦pf指向函数f,可以用下面写法调用f (*pf)(i); //也可以 pf(i); //元素是函数指针的数组,假设我们编写的程序需要向用户显示可选择的命令菜单。编写函数实现这些命令,把指向这些函数的指针储存在数组中 void (*file_cmd[])(void)={ new_cmd, open_cmd, close_cmd, close_all_cmd, save_cmd, save_as_cmd, save_all_cmd, print_cmd, exit_cmd }; //如果选择命令n,对数组取下标,调用函数 (*file_cmd[n])();//or file_cmd[n]();
受限制指针
// 如果指针p指向的对象在之后需要修改,那么该对象不允许通过除指针p之外的任何方式访问(其他访问对象的方式包括让另一个指针指向同一个对象,或者让指针p指向命名变量)。如果一个对象有多种访问方式,通常把这些方式互称为别名。 int * restrict p;
灵活数组成员c99
struct vstring{ int len; char chars[]; }; struct vstring *str = malloc(sizeof(struct vstring)+n); str ->len = n;
register 储存类型
声明变量具有register存储类型就要求编译器把变量存储在寄存器中,而不是像其他变量一样保留在内存中。
register是一种请求,而不是命令。
//例 int sum_array(int a[],int n){ register int i; int sum = 0; for(i=0;i<n;i++) sum+=a[i]; return sum; }
函数的存储类型
extern说明函数具有外部链接,也就是允许其他文件调用此函数。
static说明是内部链接,也就是说只能在定义函数的文件内部调用此函数。
如果不指明函数的储存类型,那么会假设函数具有外部链接。
解释复杂声明
int *(*x[10])(void);
无论多么复杂,下面有两条简单的规则可以用来解释任何声明
1 始终从内往外读声明符。
2 在作选择时,始终使 []和()优先于*。
内联函数 (inline function)
"内联"表明编译器把函数的每一次调用都用函数的机器指令来代替。会使被编译程序的大小增加一些,但是可以避免函数调用的常规额外开销。
inline类似于register和restrict关键之,后两者也是用于提升程序性能的,但可以忽略。
//例
inline double average(double a,double b){ return (a+b)/2; } //average有外部链接,所以在其他文件也可以调用average。但是编译器并没有考虑average的定义是外部定义(它是内联定义),所以试图在别的文件中调用average将被认为是错误的。 //两种方式避免 //1 static inline double average(double a,double b){ return (a+b)/2; } //2 更好一些的实现方式 //内联定义放入头文件(average.h) #ifndef AVERAGE_H #define AVERAGE_H static inline double average(double a,double b){ return (a+b)/2; } #endif //接下来,在创建与之匹配的源文件average.c #include "average.h" extern double average(double a,double b);
对内联函数的限制
因为内联函数的实现方式和一般函数不大一样,所以需要不同的规则和限制。
1 函数中不能定义可改变的static变量。
2 函数中不能引用具有内部链接的变量。
不完整类型
c标准对不完整类型的描述是:描述了对象但缺少定义对象大小所需的信息。
//这样做的意图是:不完整类型将会在程序的其他地方将信息补充完整 struct t; //incomplete declaration of t //因为编译器不知道不完整类型的大小,所以不能用它来声明变量 struct t s; //wrong //但是完全可以定义一个指针类型引用不完整类型 typedef struct t *T;
位运算符
移位运算符
<< //左移位 >> //右移位
注意:移位运算符的优先级比算术运算符的优先级低。
按位求反运算符、按位与、按位异或、按位或
~ //按位求反 & //按位与 ^ //按位异或 | //按位或
volatile类型限定符
volatile类型限定符使我们可以通知编译器,程序中的某些数据是“易变”的。
//volatile限定符通常使用在用于指向易变内存空间的指针的声明 volatile BYTE *p; // p will point to a volatile byte //假设指针p指向的内存空间用于存放键盘输入的最近字符。这个内存空间就是易变的。 //使用下面循环获取键入字符,并存入一个缓冲区数组中: while(缓冲区未满){ 等待输入; buffer[i] = *p; if(buffer[i++] == '\n') break; } //比较好的编译器可能会注意到这个循环没有改变p,也没改变*p,会对程序进行优化,使*p只被取一次 在寄存器中存储*p while(缓冲区未满){ 等待输入; buffer[i] = 存储在寄存器中的值; if(buffer[i++] == '\n') break; } //将p声明成指向易变数据的指针可以避免这一问题发生。
标准库的使用 (c89标准库15个 c99新增了9个 共24个)
<assert.h>//诊断 <ctype.h>//字符处理 <errno.h>//错误 <float.h>//浮点类型的特性 <limits.h>//整数类型的大小 <locale.h>//本地化 <math.h>//数学计算 <setjmp.h>//非本地跳转 <signal.h>//信号处理 <stdarg.h>//可变参数 <stddef.h>//常用定义 <stdio.h>//输入/输出 <stdlib.h>//常用使用程序 <string.h>//字符串处理 <time.h>//日期和时间 <complex.h>//复数算数 <fenv.h>//浮点环境 <inttypes.h>//整数类型格式转换 <iso646.h>//拼写转换 <stdbool.h>//布尔类型和值 <stdint.h>//整数类型 <tgmath.h>//泛型数学 <wchar.h>//扩展的多字节和宽字符实用工具 <wctype.h>//宽字符分类和映射使用工具
使用宏隐藏的函数
c标准允许在头中定义与库函数同名的宏,为了起到保护作用,还要求有实际的函数存在。声明一个函数并同时定义一个有相同名字的宏的情况并不少见。
//<stdio.h>中有如下原型 int getchar(void); //<stdio.h>通常也把getchar定义为一个宏 #define getchar() getc(stdin)
流(stream)
<stdio.h>中的许多函数可以处理各种形式的流,而不仅仅可以处理表示文件的流。
文件指针
FILE *fp1,*fp2;
标准流和重定向
<stdio.h>提供了3个标准流。
文本文件与二进制文件
<stdio.h>支持两种类型的文件:文本文件和二进制文件。
文本文件具有两种二进制文件没有的特性:
1 文本文件分为若干行
2 文本文件可以包含一个特殊的“文件末尾”
在无法确定文件是文本形式还是二进制形式时,安全的做法是把文件假定为二进制文件。
文件操作
打开文件
//filename:文件名 mode:模式字符串 FILE *fopen(const char * restrict filename,const char *restrict mode); //windows程序员注意,c语言会把字符\看成是转义序列。 fopen("c:\project\test1.dat","r"); //error //可以用\\代替\ fopen("c:\\project\\test1.dat","r"); //用/代替\ fopen("c:/project/test1.dat","r");
用于文本文件的模式字符串
"r"//打开文件用于读 "w"//打开文件用于写(文件不需存在) "a"//打开文件用于追加(文件不需存在) "r+"//打开文件用于读和写,从文件头开始 "w+"//打开文件用于读和写(如果文件存在就截去) "a+"//打开文件用于读和写(如果文件存在就追加)
用户二进制文件的模式字符串
"rb"//打开文件用于读 "wb"//打开文件用于写(文件不需存在) "ab"//打开文件用于追加(文件不需存在) "rb+"//打开文件用于读和写,从文件头开始 "wb+"//打开文件用于读和写(如果文件存在就截去) "ab+"//打开文件用于读和写(如果文件存在就追加)
关闭文件
//成功返回0,否则,返回错误代码EOF(<stdio.h>中定义的宏) int fclose(FILE *stream);
示例
#include <stdio.h> #include <stdlib.h> #define FILE_NAME "example.dat" int main(void) { FILE *fp = fopen(FILE_NAME, "r"); if (fp == NULL) { printf("Can't open %s\n", FILE_NAME); exit(EXIT_FAILURE); } //... fclose(fp); return 0; }
为打开的流附加文件
//freopen函数为已经打开的流附加上一个不同的文件。最常见的用法是把文件和一个标准流(stdin、stdout、stderr)相关联。 FILE *freopen(const char * restrict filename,const char * restrict mode,File * restrict stream); //例如,为了使程序开始往文件foo中写数据 if(freopen("foo","w",stdout)==NULL){ //error;foo can't be opened }
从命令行获取文件名
demo name.dat dates.dat
//argc是命令行的数量,argv是指向参数字符串的指针数组。 //argv[0]程序名字,argv[argc-1]都指向剩余的实际参数。 int main(int argc,char *argv[]){ .... }
临时文件
//创建一个临时文件(用"wb+"模式打开),返回文件指针 FILE *tmpfile(void); //为临时文件产生名字。 char *tmpnam(char *s); //如果它的参数是空指针,那么tmpnam会把文件名储存到一个静态变量中,并返回指向此变量的指针 char *filename; ... filename = tmpnam(NULL); //creates a temporary file name //否则,tmpnam函数会把文件名复制到程序员提供的字符数组中 char filename[L_tmpnam]; ... tmpnam(filename);//creates a temporary file name
文件缓冲
int fflush(FILE *stream); void setbuf(FILE * restrict stream,char * restrict buf); void setvbuf(FILE * restrict stream,char * restrict buf,int mode,size_t size);
fflush可以按我们希望的频率来清洗文件的缓冲区。
fflush(fb); //flushes buffer for fb fflush(NULL); //flushes all buffers
setvbuf函数允许改变缓冲流的方法,并且允许控制缓冲区的大小和位置。
第三个参数指明了期望的缓冲类型,三个宏:
1 _IOFBF(满缓冲)。
2 _IOLBF(行缓冲)。
3 _IONBF(无缓冲)。
第二个参数是期望缓冲区地址。可以是静态储存期限,自动储存期限,甚至可以是动态分配的。
最后一个参数是缓冲区内字节的数量。
例如
//利用buffer数组中的N个字节作为缓冲区,而把stream的缓冲变成了满缓冲 char buffer[N]; ... setvbuf(stream,buffer,_IOFBF,N);
setbuf函数是一个较早期的函数,缓冲模式和缓冲区大小默认。
//buf是空指针,等价于 (void)setvbuf(stream,NULL,_IONBF,0); //否则等价于 (void)setvbuf(stream,buf,_IOFBF,BUFSIZ);
其他文件操作
//删除文件 int remove(const char *filename); //重命名 int rename(const char *old,const char *new);
检测文件末尾和错误条件
void clearerr(FILE *stream); int feof(FILE *steam); int ferror(FILE *steam);
字符的输入/输出
输出函数
int fputc(int c,FILE *stream); int putc(int c,FILE *stream); int putchar(int c);
输入函数
int fgetc(FILE *stream); int getc(FILE *stream); int getchar(void); int ungetc(int c,FILE *stream);
行的输入/输出
输出函数
int fputs(const char * restrict s,FILE * restrict stream); int puts(const char *s);
输入函数
char *fgets(char * restrict s,int n,FILE * restrict stream); char *gets(char *s);
块的输入/输出
freade和fwrite函数可以用于文本流,但是它们主要还是用于二进制流
size_t fread(void * restrict ptr, size_t size,size_t nmemb,FILE * restrict stream); size_t fwrite(const void * restrict prt,size_t size,size_t nmemb,FILE * restrict stream);
文件定位
int fgetpos(FILE * restrict stream,fpos_t * restrict pos); int fseek(FILE * stream,long int offset,int whence); int fsetpos(FILE * stream,const fpos_t *pos); long int ftell(FILE * stream); void rewind(FILE *stream);
字符串的输入/输出
输出函数
int sprintf(char * restrict s,const char * restrict format,...); int snprintf(char * restrict s, size_t n,... const char * restrict format,...);
输入函数
int sscanf(const char * restrict s,const char * restrict format,...);
其它
eclipse cdt导入c项目
右键Properties->c/c++ Build->Settings->Build artifact->Artifact type->Executable