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,提供了几种常见的通信模式

  libwebsockets 
 
静态局部变量
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

 

posted on 2018-06-22 10:56  1zfang1  阅读(250)  评论(1编辑  收藏  举报

导航