浙大mooc翁凯 C语言笔记

C语言学习笔记

结构体(struct)

结构体的嵌套

struct point{
    int x;
    int y;
};//定义平面上的点
struct rectangle{
    struct point p1;
    struct point p3;
}//已知斜对角两点,可定义一个矩形

如果有struct rectangle r;,就可以有r.p1.xr.p1.yr.p2.xr.p2.y
如果有变量定义:

struct rectangle r, *rp;
rp = &r;

注意:r.p1.xrp->p1.x是两种等价的形式。rp->p1->x写法错误,因为p1不是指针。

//对嵌套的结构体赋值
struct rectangle r = {{-1,0},{{1,1}};
struct rectangle react[]={
    {{-1,0},{{1,1}},
    {{2,1},{{3,7}}
};

自定义数据类型

C语言提供 typedef 的功能来声明一个已有的数据类型的新名字
typedef int Length使得Length成为int类型的别名

typedef int Length;
Length a, b, len;
typedef struct ADate{
    int x;
    int y;
} Date;//Date 为struct ADate 的新名字
int main()
{
    Date test = {1, 2};//利用Date 定义结构体变量test
    printf("x=%d y=%d\n", test.x, test.y);
}
//为字符串类型取名
    typedef char *string;
    string s = "hello";
    typedef char strings[10];//string为长度为10的字符串类型
    strings s2 = "world";
    printf("s=%s s2=%s\n", s, s2);

联合(union)

union anelt {
    int i;
    char c;
} elt1, elt2;
    elt1.i = 4;
    elt2.c = 'a';
    printf("size=%d\n",sizeof(union anelt));//size=4

上面代码的含义是anelt的成员是一个int或是一个char,union类型的大小由成员中最大值决定

typedef union{
    int i;
    char ch[sizeof(int)];
} CHI;

    CHI chi;
    chi.i = 1234;
    int i;
    printf("i=0x%X\n", chi.i);//1234的16进制为0x000004D2
    for (i = 0; i < sizeof(int); i++){
        printf("%02hhX", chi.ch[i]);
    }
    printf("\n");

x86cpu为小端机,内存存放数时 低位在前 ,故chi.ch[i]输出了D2040000,说明chi.ch与chi.i公用同一处内存空间。

全局变量

全局变量初始化:
1.没有做初始化的全局变量会得到0值,指针会得到NULL
2.只能用编译时刻已知的值来初始化全局变量
3.它的初始化在main函数之前

int f(void);
int gall = 12;
int main()
{
    printf(" in %s gall1=%d\n", __func__, gall);//gall = 12
    f();//gall = 14
    printf(" in %s gall2=%d\n", __func__, gall);//gall = 14
}
int f(void){
    gall += 2;
    printf(" in %s gall_f=%d\n", __func__, gall);
}

全局变量的赋值不能是变量

int gall = 12;
int gall2 = gall;//gall是变量,会报错
int gall3 = f();//同样会报错

正确的做法是

const int gall = 12;
int gall2 = gall;

还需要注意,如果函数内部存在与全局变量同名的变量,全局变量会被隐藏

静态本地变量

  • 静态本地变量实际上是特殊的全局变量,位于相同的内存区域
  • 静态本地变量具有全局的生存性,函数内的局部作用域
int gall = 100;
int f(void);
int main()
{
    f();//i=2
    f();//i=3
}
int f(void){
    static int i=1;//不加static每次运行函数会初始化i为1
    printf("&gall=%p &i=%p\n", &gall, &i);
    i++;
    printf("i=%d\n", i);
}
//&gall=403014 &i=403010刚好相差4个字节,故i为全局变量

返回指针的函数需要注意:

  • 返回本地变量的地址是危险的
  • 返回全局变量或静态本地变量的地址是安全的
  • 安全的做法是返回传入的指针

宏定义(编译预处理指令)

  • #开头的是编译预处理指令
  • #define用来定义一个宏
    • #define <名字> <值>
    • C语言的编译器开始编译之前,编译预处理软件会将程序中的名字换成值(完全的文本替换)
    • 值可以为任何东西(包括命令)
#define PI 3.14
#define PI2 2*PI
int main()
{
    printf("%f\n", 2*PI2*3.0);
    return 0;
}//输出37.68
#define PI 3.14
#define PI2 printf("%.2f\n", PI);\
            printf("%.2f\n", 2*PI)
int main() { PI2; }//输出3.14和6.28

预定义的宏

  • __LINE__源代码文件的行号(整数)
  • __FILE__文件名称(字符串)
  • __DATE__编译文件时的日期(字符串)
  • __TIME__编译时的时间(字符串)
  • __FUNCTION__ __func__返回程序名(字符串)
    使用宏注意加上括号
#define L(x) x*10
#define L2(x) (x)*10
    printf("%d\n", L(2 + 5));//输出52
    printf("%d\n", L2(2 + 5));//输出70

多个源代码文件

有两个文件get.c``main.c分别为

void get(void){
    printf("运行成功");
}
#include
#include "get.c"
int main(){ get(); }//运行成功

多个C文件的调试

法一:

  1. 将多个C项目文件放入同一个新建文件夹project。我们以文件main.cget.c为例子
//main.c
#include
#include "get.h"
int main()
{
    get();
}
//get.c
#include"get.h"
void get(void){
    printf("congratulation!");
}
//get.h
void get(void);
  1. 在终端中输入以下命令用于生成编译文件my_multi.exe
gcc -g .\main.c .\get.c  -o my_multi
  1. 在运行和调试栏处新建gcc.exe的配置文件lanuch.json(前提是已经将mingw64\bin加入了环境变量)
  2. 打开lanuch.json找到"preLaunchTask"并将改行注释掉
  3. "cwd"行改为"${workspaceFolder}";然后将"program"行改为编译执行文件目录"${workspaceFolder}/my_multi.exe"
    完成这些就可以顺利调试了

法二 CMake

  1. 新建CMakeLists.txt在其中写入
project(MYMULTI)
add_executable(my_cmake_multi main.c get.c)
//包含生成文件,两个需要编译的c文件
  1. Ctrl+Shift+p调出命令栏,输入cmake:Configure,选择gcc编译器。成功后会生成build文件夹
  2. 在终端处进入build文件夹输入cmake ..再输入mingw32-make.exe。终端最后一行显示Built target my_cmake_bulti则表示创建编译文件成功。
  3. 重复方法一中的操作5,将"program"后改为my_cmake_bulti.exe文件的路径即可进行调试。

build文件

不用cmake自己创建build文件。在终端project处输入

mkdir build
cd build
cmake -G "MinGW Makefiles" .. //这一步是为了使程序调用gcc编译器
mingw32-make.exe

文件输入和输出

格式化输入和输出

printf %[flags][width][.prec][hIL]type

printf("%-9d\n", 123); //靠左对齐输出
123
printf("%9d\n", 123);
     123
printf("%+9d\n", 123);//表示前面保留加号或减号
    +123
printf("%-+9d\n", 123);
+123     //
printf("%09d\n", 123);//空的位置填上0
000000123
printf("%hhd\n",12345);//0x3039只取一个字节的部分39,变成十进制是57

hIL类型修饰

类型修饰 含义
hh 单个字节
h short
l long
ll long long
L long double

文件

fopen 函数

FILE *fopen(const char *filename, const char *mode)输入文件的路径,打开方式,返回文件指针。"r"模式下,文件若不存在则返回NULL

  • filename–这是C语言字符串,包含了要打开的文件名称。
  • mode–这是 C 字符串,包含了文件访问模式,模式如下:
模式 描述
“r” 打开一个用于读取的文件。该文件必须存在。
“w” 创建一个用于写入的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件。
“a” 追加到一个文件。写操作向文件末尾追加数据。如果文件不存在,则创建文件。
“r+” 打开一个用于更新的文件,可读取也可写入(不删除原有数据)。该文件必须存在。
“w+” 创建一个用于读写的空文件,若文件已经存在则删除已有内容
“a+” 打开一个用于读取和追加的文件。没有则会新建一个文件

fscanf

int fscanf(FILE *stream, const char *format, ...)
返回:如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。

    fp = fopen("F:\\VScode\\VScode-C\\test.txt", "a+");
    printf("name\t num\t score\n");
    for (i = 0; i < 3;i++){
        fscanf(fp,"%d%s%d", &num, name, &score);
        printf("j=%d\n", j);//j=3,返回成功酦醅和赋值的个数
        printf("%s\t%d\t%d\n", name, num, score);
    }
    fclose(fp);

fprintf

int fprintf(FILE *stream, const char *format, ...)

  • stream为指向FILE对象的指针
    如果成功,则返回写入的字符总数,否则返回一个负数。
   fp = fopen ("file.txt", "w+");
   fprintf(fp, "%s %s %s %d", "We", "are", "in", 2014);//输出为We are in 2014

下面为示例:

#include
int main()
{
    FILE *fp;
    int i, n;
    int num, score;
    char name[20];
    fp = fopen("test.txt", "w+");
    printf("你想输入多少组数据:");
    scanf("%d", &n);
    for (i = 0; i < n;i++){
        printf("姓名:");
        scanf("%s", name);
        printf("学号:");
        scanf("%d", &num);
        printf("分数:");
        scanf("%d", &score);
        fprintf(fp, "%s\t%d\t%d\n", name, num, score);
    }
    fseek(fp, 0, SEEK_SET);
    printf("name\tnum\tscore\n");
    for (i = 0; i < n;i++){
        fscanf(fp, "%s\t%d\t%d\n", name,&num,&score);//fscanf会改变指针位置
        printf("%s\t%d\t%d\n", name, num, score);
    }
    fclose(fp);
}

sprintf

int sprintf(char *str, const char *format, ...)

fwrite

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)

  • ptr – 这是指向要被写入的元素数组的指针。
  • size – 这是要被写入的每个元素的大小,以字节为单位。
  • nmemb – 这是元素的个数,每个元素的大小为 size 字节。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
    返回:写入成功返回写入字符总数(不包括末尾空字符),失败则返回负数。
    FILE *fp;
    fp = fopen("file.txt", "w");
    char str[] = "This is runoob.com";
    fwrite(str, sizeof(str), 1, fp);
    fclose(fp);

注意:fprintf将每一位用ASCII码储存,然后txt将ASCII码翻译位字符输出;而fwrite是直接将字符写入,例如65,65为A的ASCII码,txt中写为A

fread

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

  • ptr – 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
  • size – 这是要读取的每个元素的大小,以字节为单位.
  • nmemb – 这是元素的个数,每个元素的大小为 size 字节。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
    返回成功读取的元素总数
FILE *fp;
   char c[] = "This is runoob";
   char buffer[20];
   FILE *fp;
   /* 打开文件用于读写 */
   fp = fopen("file.txt", "w+");
   /* 写入数据到文件 */
   fwrite(c, strlen(c) + 1, 1, fp);
   /* 查找文件的开头 */
   fseek(fp, 0, SEEK_SET);
   /* 读取并显示数据 */
   fread(buffer, strlen(c)+1, 1, fp);
   printf("%s\n", buffer);
   fclose(fp);

fseek

int fseek(FILE *stream, long int offset, int whence)

  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
  • offset – 这是相对 whence 的偏移量,以字节为单位。
  • whence – 这是表示开始添加偏移 offset 的位置。
    1. SEEK_SET文件开头
    2. SEEK_CUR文件指针的当前位置
    3. SEEK_END文件末尾
  • 注意:利用a+a模式打开的文档,SEEK_SET就是在追加的起始位置而非文档开头
    如果成功,则该函数返回零,否则返回非零值。

ftell

long int ftell(FILE *stream)该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。因此ftell可以得到文本大小

#include
int main()
{
    FILE *fp = fopen("test.txt", "w+");
    int loc = ftell(fp);
    printf("loc=%d\n", loc);//loc=0
    fprintf(fp,"abcde");
    int loc2 = ftell(fp);
    printf("loc2=%d\n", loc2);//loc2=5
}

位运算

按位与&

若(x)i=1且(y)i=1,则(x&y)i=1,否则(x&y)i=0

  • 利用0xFF对某个数做按位与运算,可以取得其后8位bit的情况
  • 利用0xFE可以使数的最后一位bit为0

按位取反~

(~x)i=1-(x)i

c = 0xaa  10101010
~c = 0x55 01010101
-c = 0x56 01010110  //-c=2^8-c

按位异或(^)

若(x)i=(y)i,则(xy)i=0,否则(xy)i=1

1^0^0=1
0^0^0=0
1^1^1=1
0^1^1=0
(x^y)^y=x
x^(~x)=0xFF

左移(<<)与右移(>>)

i<i所有位左移j位,右边填0
右移:unsigned型左填0;signed型左填原来的最高位。小于int类型的数以int来做

unsigned char b = 0xA5;
printf("b<<2=%hhx\n", (char)b<<2); //0x94
int a = 0x80000000;   //2^31,signed型,保留第32个bit上的1,右移后第31个bit也变为1,结果为-2^30
unsigned int d = 0x80000000;//直接右移,最高位bit变为0
printf("a=%d\n", a>>1);//-1073741824
printf("d=%d\n", d>>1);//1073741824

位段

下面的struct结构定义位段,每一位为1bit,从右向左排列,当所需位超过一个int时会采取多个int

#include
struct U0{
    unsigned int leading : 3;
    unsigned int FLAG1 : 1;
    unsigned int FLAG2 : 1;
    int trailing : 27;
};
void prtbin(unsigned int number);

int main()
{
    struct U0 uu;
    uu.leading = 3;
    uu.FLAG1 = 1;
    uu.FLAG2 = 0;
    uu.trailing = 0;
    printf("sizeof(uu)=%d\n", sizeof(uu));
    prtbin(*(int *)&uu);           //将uu指针变成整型指针输出,结果前五位01011
}
void prtbin(unsigned int number)  //以二进制方式打印整数
{
    unsigned m = 1u << 31;
    for (; m;m>>=1){
        printf("%d", number & m ? 1 : 0);
    }
    printf("\n");
}

数的二进制转换

#include
int main(){
    int n=0xaaaaaaaa ;//10101010101010101010101010101010
    //scanf("%d", &n);
    unsigned m = 1u << 31;
    for (; m;m>>=1){
        printf("%d", n & m ? 1 : 0);
    }
    printf("\n");
}

链表

可变数组

设计一组函数,他们能提供可变大小的数组。他们需要满足:可变大小、能得到当下数组的大小、能获取里面的元素

  • Array array_creat(int init_size);创建指定大小数组
  • void array_free(Array *a); 将数组空间回收
  • int array_size(const Array *a); 得到数组内可用单元
  • int* array_at(Array *a, int index); 访问数组中的某个单元
  • void array_inflate(Array* a, int more_size); 使数组空间扩充

PS:空指针表示==“未分配”或者“尚未指向任何地方”。它与未初始化的指针有所不同,空指针可以确保不指向任何对象或函数==,而未初始化指针可能指向任何地方。空指针与任何对象或函数的指针都不相同,malloc成功调用和&成功取址都不会得到空指针

链表

每一个节点都是结构体,包含两部分,一部分位值value,一部分为指向下一个结构体的指针

#include
#include

typedef struct _node{
    int value;
    struct _node *next;
} Node;

int main()
{
    Node *head = NULL;//定义指向第一个节点的指针head
    int number;
    do{
        scanf("%d", &number);
        if (number!=-1){
            Node *p = (Node *)malloc(sizeof(Node));
            p->value = number;
            p->next = NULL;
            Node *last = head;  //让last从head通过循环到达最后一个节点
            if (last){
                while(last->next){
                    last = last->next; //保持last为最后一个节点的指针,next为NULL
                }
                last->next = p;//将最后一个节点中的next指向新的节点
            } else {
                head = p;   //注意在第一次赋值时将指针p交给head
            }
        }
    } while (number == -1);
}
include<stdio.h>
#include

typedef struct _node{
    int value;
    struct _node *next;
} Node;
typedef struct{
    Node * head;
}List;
//Node *add(Node *head, int number);
void add(List* plist, int number);

int main()
{
    List list;
    list.head= NULL;
    int number;
    scanf("%d", &number);
    while(number!=-1){
        //head = add(head, number); //对链2表输入值,输入-1表示结束
        add(&list, number);
        scanf("%d", &number);
    }
    Node *p;
    int count = 0;
    for (p = list.head; p;p = p->next,count++){
        printf("%d:%d\n", count+1,p->value);
    }
    Node *q;
    printf("请输入你想删除的数:");
    scanf("%d", &number);
    for (q = NULL, p = list.head; p;q = p,p = p->next){ 
        if(p->value==number){  //注意保证箭头左侧的指针不能为NULL
            if(q){
                q->next = p->next;//找到之后将p之前节点的结构体的next变量换成p之后节点的指针
            } else{
                list.head = p->next; //当需要删除的节点位于第一个位置时
            }
            free(p);
            break;   
        }
    }
    count = 0;
    for (p = list.head; p;p = p->next,count++){
        printf("%d:%d\n", count+1,p->value);
    }
    for (p = list.head,q = NULL; p;p = q){  //删除整个链表
        q = p->next;
        free(p);
    }
}

void add(List* plist,int number){
    Node *p = (Node*)malloc(sizeof(Node));
    p->value = number;
    p->next = NULL;
    Node *last = head;
    if(last){
        while(last->next){
            last = last->next;
        }
        last->next = p;
    }else {
        head = p;
    }
}
/* Node* add (Node*list,int number);函数有一个缺点,他是由另一个指针变量接受head,若不反回head,则可能因为最初head=NULL使程序错误
一个好办法是定义一个结构体
typedef struct{
    Node * head;
}List; 
然后传入List *plist,在add函数中对plist->head = p,这样add类型可以是void*/

你可能感兴趣的:(c语言)