C语言从入门到项目实战学习笔记

C语言

    • 概述
    • 数据类型与运算
    • 简单程序设计思想
    • 程序结构之循环
    • 函数
    • 真的会数组么
    • 终极进化之指针
    • 结构体
    • 文件操作
    • 进阶提高篇
    • 项目篇
    • 问题及困难及下一步考虑

概述

这里针对《C语言从入门到项目实战》整理出C的学习笔记,以备以后知识点复习,顺便给入门的小伙伴一个学习参考。

C语言特性

环境搭建:[https://blog.csdn.net/helloworld9998/article/details/103596018]

输入输出
多文件编译两种方法
linux 下include 包含的文件在 /usr/include 中
windows 下 include文件和编辑器有关,一般都在编辑器的目录下,比如codeblocks在Mingw的include。
在C盘下搜索一个系统函数如“stdio.h”,就会知道它的路径了
常用的库stdio.h windows.h unistd.h stdlib.h conio.h system函数
连接编译 比如使用了windows下的winmm库,也就是winmm.dll,编译需要在命令中使用gcc xx.c -o xx.exe -lwinmm

为windwos 查找文件
dir c:\winmm.dll /s /b 在c盘中找winmm。dll
/s 显示指定目录和所有子目录中搜索匹配。
/b 不会显示标题和盘符信息等

在内容中查找
find /N /I “system” a.c
/N 显示行号
/I 忽略大小写
/V 不包含内容的其他信息,反向查找

删除隐藏文件
attrib 查看文件属性
del /ah 文件名
或者更改属性,再删除
具体:
https://blog.csdn.net/dodott/article/details/81183801

数据类型与运算

%d,%o %x %X 十六进制大写 带有进制标识 %#o,%#x,%#X
long 整形形使用%ld long long占8个字节 ,使用%lld
bool型变量,在stdbool.h头文件,直接使用 bool a =true, a的值其实就是1。
char 型字符型,其实在也一个整数类型,使用时注意要用单引号。char a = ‘a’
int x= ‘ABCD’ printf("%x",x) -----输出41424344 把4个独立字符放在一个32位int类型变量中。

浮点型变量 输出用 %f 科学计数法%e double e=234.123e9,long double型使用%Lf,%Le输出。
double 数据输入输出使用%lf,但一般在输出时使用%.5f,来保留一定位数的小数。
数据类型的隐式转换,int double 取模必须为整数
使用++ – 提高执行效率,使用一条机器指令,而i=i+1。需要三条指令。

运算符优先级

&& 和 || 是逻辑运算符的短路,只要左边的值解决整个计算结果,不进行右边计算。
!x 如果x为0 值为1,否则为0
K || i++ && j-3, 即使 k成立,不会计算i++,但是会计算j-3
sizeof 结果为size_t 类型,定义在stddef.h及其他标准头文件被定义为无符号整数类型。
三元运算 表达式1 ? 表达式2 : 表达式3 如果表达式计算完不等于0 ,执行表达式2,等于0 ,执行表达式3。

简单程序设计思想

数据结构+算法=程序
数据结构:处理对象(数据)如何组织
算法:做什么,怎么做,解决问题的方法和步骤。

多读算法和书,有助于解决问题,写出好的算法,好的程序。

算法五要素:
输入 输出 有穷性 确定性 可行性

流程图表示法:
起止框 输入输出框 处理框 判断框

switch 语句中
判断表达式为一个值, case和default次序任意,多个case可以公用一个语句,每个语句后有break,否则会直接执行后面case语句,直到找到break,或者switch右括号,多有多个语句时,不需要使用大括号括起来。

程序结构之循环

do while 循环是循环体总要被执行一次。循环次数已知,最好用for循环。
for 循环中变量的作用域在for的大括号中。
推断罪犯问题: p98页;p103问题抽象为代码实现。
打印肥波那契数列打印前50。

函数

函数名代表了函数对应代码在内存中存储首地址
建议定义函数时,避免函数内变量名和函数名重复
在程序中调用函数时,如果函数未定义,即该函数的定义在调用处后面,这时需要对函数进行声明,声明目的在于告诉编译器函数名是什么?形式参数有几个,形参类型,返回值类型。这样编译器遇到调用该函数时,会对上面几项进行检查,如果函数定义在调用处前面则不需要声明。

一般建议在函数之前声明函数,在其他文件定义和函数。
返回类型 函数名(形参类型,形参类型…)
形参一般值传递,如果形参为指针,则指向实参地址,改变的是实参的值。
递归就是函数调用自己。找出递归关系,要有递归出口。
全局变量和静态局部变量(static) de 生存周期为程序的整个周期,直到程序从内存中退出。
全局变量就是任何函数之外的变量。当全局变量和局部变量重名时,在局部变量作用域内访问的是局部变量。
如果在其他文件应用全局变量,需要在使用的函数中或者其他文件中用extern关键字修饰扩展全局变量作用域,如果全局变量或者函数用static修饰,则为静态变量或者函数,只能在本文件内使用,不能被其他文件所调用。
静态局部变量作用域为函数内部,变量分配在全局数据区全局变量和静态局部变量未赋初始值时,编译器会自动初始化为0。

C语言由源代码生成可执行文件过程:
C源程序->预处理->编译->优化程序->汇编程序->链接程序->可执行文件。其中把预处理,编译,优化,汇编统称为汇编阶段。
预处理指令分为4类:包含头文件,宏定义及宏展开,条件编译,特殊符号处理。
预处理指令不能用分号作为结束符。
#include 预编译编译在编译器自带的或者外部库查找头文件
#include “xxx” 现在当前文件目录查找,找不到再去编译器自带头文件查找。
宏:
#define 标识符 特定字符串
1,无分号
2,字符串可是数字,字符串,函数。
3,如果为函数,记得把函数的语句用括号括起来。
#define VERSION “Version 1.0 copyright© 2019”
#define PI 3.1415
#define PRINT(x) printf("%d",x)
#define CUBE() (x*x**x)

#define
#undef 取消宏定义
条件编译
#if 检测后面常量表达式,直到出现#else #elif #endif为止,否则不编译
#endif 终止#if预处理指令

#ifdef 常用来检测宏是否定义。
#else ,用在#if后,if的不满足,就编译else后面的代码
#elif 嵌套条件编译
#elifdef
#endif

#ifdef 等价于 #if defined
#ifndef等价于 #if !defined,

预处理特殊符号:

__FILE__   包含当前文件名的字符串
__TIME__
__DATE__
__LINE__   当前行号

#error
#error Compile Version is not provided! 使编译显示一条错误,并停止编译

#pragma pack——用于指定内存对齐方式 ,#error #line #pragma使用具体参考如下
https://www.cnblogs.com/CoderTian/p/5903280.html

模块化编译连接:
多文件编译时,
把每个文件编译成二进制代码:
gcc -c xxx.c -o xxx.o
再把所有。o链接为可执行程序
gcc xxx.o xx.o -o xx.exe
其中.o文件不可执行,因为还没有把诸如printf()等这些库函数的二进制实现代码链接起来。

源码编译进程序
gcc -g xxx.c -o xxx.exe

真的会数组么

数组:用来存放一组相同类型数据的数据类型。数组名是一个常量,表示第一个元素地址,不能出现在括号左边。
比如: char a[100]; a=“aaaa”;编译的时候是错误的。

数组最低地址对应第一个元素,最高地址对应最后一个元素。
系统为数组申请 数组长度*sizeof(元素类型)个连续的内存单元,数组名为数组首地址,比如

int test[10] test 指向第一个元素,test+1 指向第二个元素,*(test+1) 等价于 test[1],在内存中,第二个元素的地址为第一个元素加上int类型占用的4个字节。

数组的初始化 int a[10] ={0},所有值为0。int b[]={1,2,3,4]
也可以用sizeof(test)/sizeof(int) 求数组总长度,如果你初始化了5个元素,前面结果还是10。

int a[5]={0};
int b[5}={1};
a=b;
是错误的,不可以对两个数组直接进行赋值,因为阿a代表数组第一个元素地址,是一个常量,不可出现在左边。
如果是字符数组,char str1[]=“asdfasdfasdfasfd” 长度为sizeof(str1)/1,结果还是sizeof(str1).
冒泡排序:

c语言不对数据形参下标边界检查,通常还需要传递一个表示数组大小的值。
二维数组元素在内存中是连续存放的,按行优先存储,比如int a[3][4] 先放a[0] 行,在放a[1],然后a[2],元素都是连续存放,设一个数组为a[M][N] ,则a[i][j] 的地址为 a + (i*N+j)*4
赋值的时候,
int a[4][3] ={{1,2,3,},{4,5,6},{7,8,9},{11,12,13}}
int a[4][3]={1,2,3,4,5,6,7,8,9,10,11,12}
int a[4][3] = {{1},{2},{3}} 只对每一行第一个元素赋值
如果对全部元素赋值,则一维长度可以不写
int a[][4] = {1,2,3,4,5,6,7,8,9,10,11,12}

二维数组作为函数的形参的几种定义形式

方法一: 形参给出第二维的长度。
类型 数组名[一维长度] [二维长度]
类型 数组名 [] [二维长度]

方法二:形参声明为指向数组的指针。
类型  (*数组名)[二维长度]

方法三:形参声明为指针的指针。
类型 ** 数组名
这种形式,不能使用如array[i][j] 对数组取赋值,应该使用array[i*j +j],
看作为一个一维数组进行访问,总长度为i*j。

字符串使用字符数组来存储,在如果字符串有N个字符,则需要长度N+1个字符数组存储,因为末尾需要加“\0"
在定义时进行初始化,不可以往数组名赋值,因为数组名是常量。
数组输入:
如果数组中没有空格:

char str1[100];
scanf("%s",str1);

如果有空格:

#include 
gets(str1);

输出:

printf(“%s",str1);
or
puts(str1);

字符串操作:在string.h中添加链接描述

char * strcat(cahr * dest,const char* src),将src附加到dest,返回dest
char * strcpy(char * dest, const char* src) ,将src拷贝到dest,返回dest
int  strcmp(const char * str1,const char* str2),
比较str1,str2,相等返回0,前者大,返回正数,后者大返回负数。

终极进化之指针

指针就是内存地址,指针变量就是存储地址的变量

指针变量定义:
数据类型 * 变量名 [=初值]
数据类型是指针变量存储的值的类型
&取址符 ,*取值符 。
指针变量空间大小,sizeof(指针) ==4;表示4个字节。
指针变量多用于函数形参,通过传递实参地址给形参,达到修改变量目的。

定义指针时,最好把指针赋值为NULL

指针,指针变量和变量,一维数组,二维数组,结构体在内存中的分布详细探讨:
https://www.cnblogs.com/souhaite/p/10929781.html
http://c.biancheng.net/view/246.html
https://blog.csdn.net/qq_39883358/article/details/86765695
https://blog.csdn.net/constantin_/article/details/79575638
https://www.jianshu.com/p/5f2396ae5466

memset()使用:
void * memset(void *s,int c,size_t n);
常用来对数组内容清零,
比如: 
从a的起始地址开始,把0填充到 10个int类型内存空间,也就是40个字节。
int a[10];
memset(a,0,10*sizeof(int));

void * 指针为万能指针,可以通过强制转换为任何其他类型指针。在使用时,必须要把void *型指针强制转换为具体类型,比如:

    int b =6 ;
    void *a=&b;
    printf("%d\n",*(int *)a);
    //*a是不合法的,因为编译器不知道void指针存储的类型是什么
    //强制转换为(int *) int型,编译器知道指针存储的数据类型为int
    //典型例子为malloc函数返回void 类型,所以必须强制转换

动态内存分配:

malloc()使用:
void * malloc(size_t size);
返回被分配内存的地址,初始值不确定,否则返回NULL,
使用完应该使用free释放掉,操作系统不会自动回收动态分配的内存。
void free(void *ptr)

例子:

void my_dy_array()
{
    int n;
    int sum =0;
    printf("Please input array length\n");
    scanf("%d",&n);
    int * array=NULL;
      array = (int *) malloc(n*sizeof(int))  ;
      memset(array,0,n*sizeof(int));
      printf("Please input %d numbers\n",n);
      for(int i =0;i<n;i++)
      {
          scanf("%d",array+i);
          sum+=*(array+i);

      }
      printf("sum ==%d\n",sum);

      free(array);
}

输出:

Please input array length
5
Please input 5 numbers
11 12 13 14 15
sum ==65

动态分配内存时,存放字符串的末尾需要加上’\0’,注意是单引号。
例子:

void rand_str()
{
    int len;
    printf("Please input length of str\n");
    scanf("%d",&len);
    char *buffer=NULL;
    buffer = (char * )malloc(len+1);
    srand(time(0));
    if(buffer==NULL)
    {
        exit(1);
    }
    memset(buffer,'a',len+1);
    for(int i =0;i<len;i++)
    {
       buffer[i]=rand( )% 26+'a';
    }
  buffer[len]='\0';
    printf("%s\n",buffer);
   // printf("%d",rand());
    free(buffer);
}

输出:

Please input length of str
10
pexxhblpdy

const指针常量:
1,当使用const 修饰 * 时,表示这个指针指的数据内容不能修改,但是指针的地址可以改。多用在函数形参中,保证指针的值不被修改。

    int a =5,b=6;
    int const *p=&a;
    *p=b;//wrong
    p=&b;

2, 当使用const 修饰 指针名时,表示这个指针的地址不能改变,但是指针所指数据内容可以改

    int a =5,b=6;
    int * const p2=&a;
    p2=&b;// wrong
    *p2 =9;

3,指针常量即是常量指针,又是常量指针变量。

    int const * const p3=&a;
    *p3 =0;//wrong
    p3=&b;//wrong

指针访问一维数组,数组名就是指向数组首地址的指针,数组名++,表示指向下一个元素地址。

void my_array_p()
{
    int a[5]={1,2,3,4,5};
    int *p=a;
    p[2] =333;
    *(p+3) = 444;
    printf("addres of  a ==%p\n",a);
    for(int i = 0;i<5;i++)
    {
        printf("a[%d] address ==%p, a[%d]==%d\n",i,p+i,i,*(p+i));
        //%p以十六进制整数方式输出指针地址,用%x ,%X, %#X 都可以。
        // printf("a[%d] address ==%p, a[%d]==%d\n",i,p,i,*(p));
         //p++;
    }
}

输出:

addres of  a ==0060FEB4
a[0] address ==0060FEB4, a[0]==1
a[1] address ==0060FEB8, a[1]==2
a[2] address ==0060FEBC, a[2]==333
a[3] address ==0060FEC0, a[3]==444
a[4] address ==0060FEC4, a[4]==5

数组指针:
首先是一个指针,指向数组。定义如下:

类型名 (* 标识符)[数组长度]

主要用途:用来指向二维数组中的某一行。

一般来说,在指针访问二维数组时:
p表示二维数组中的行地址
*p表示二维数组中的列地址

二维数组的指针使用,举一个例子说明:

#include 
#include 

int main() {
    int c[10];
    memset(c,'B',sizeof(int)*10);
    int a[3][5] = {{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}};
    int (*p)[5] =a;
    for(int i=0; i<3; i++) {
        for(int j =0; j<5; j++) {
            printf("&P-->%p   P-->%p   P+i-->%p   *(P+i)-->%p   *(P+i)+j-->%p    *(*(p+i)+j)-->%d\n",&p,p,p+i,*(p+i),(*(p+i)+j),*(*(p+i)+j));
        }
    }
    int b[10];
    memset(b,'A',sizeof(int)*10);

    return 0;
}

输出:

C语言从入门到项目实战学习笔记_第1张图片
内存:

C语言从入门到项目实战学习笔记_第2张图片
二维数组作为函数形参的几种方式:

例子:
第一种方式,用数组指针作为形参,可以使用p[i][j]形式访问元素,列长度必须声明:

  void test_2_array()
{
    int a[5][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12},{13,14,15}};
    printf("sum == %d\n",sum(a,5));

}

int sum(int (*p)[3],int n)
{
    int sum1=0;
    for(int i =0;i<n;i++)
    {
        for(int j =0;j<3;j++)
        {
            sum1+=*(*p+j);

        }
        p++;
        //p+=i;
    }
    return sum1;
}

第二种方式:
使用二级指针作为形参,不可使用p[i][j]形式访问元素,需要进行强制转换

void test_2_array()
{
    int a[5][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12},{13,14,15}};
    printf("sum == %d\n",sum(a,5));

}

int sum(int **p,int n)
{
    int sum1=0;
    int col = 3;
    for(int i =0;i<n;i++)
    {
        for(int j =0;j<col;j++)
        {
            sum1+=*((int *)p+i*col+j);//强制转换p为一维数组指针

        }

        //p+=i;
    }
    return sum1;
}

第三种
传数组,但是列长度必须给出:

void test_2_array()
{
    int a[5][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12},{13,14,15}};
    printf("sum == %d\n",sum(a,5));

}
int sum(int p[][3],int n)
{
    int sum1=0;
    for(int i =0;i<n;i++)
    {
        for(int j =0;j<3;j++)
        {
            sum1+=p[i][j];

        }
    }
    return sum1;
}

指针数组:
首先是一个数组,数组中的元素为指针。
例子:

void  test_p_array()
{
    int x =5;
    int *p[3]={NULL};//声明并赋初值
    p[0]= (int *)malloc(7*sizeof(int));
    p[1]= (int *) malloc(5*sizeof(int));
    p[2] =&x;
    for(int i =0;i<3;i++)
    {
        printf("p[%d] ==%#X\n",i,p[i]);
    }
    free(p[0]);
    free(p[1]);
}

输出:

p[0] ==0X6C0D50
p[1] ==0X6C0D78
p[2] ==0X61FEC8

指针数组也可用于指向多个字符串,使得字符串处理更加灵活:

void test_str_array()
{
    char *weeks[7]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
    for(int i =0;i<7;i++)
    {
        printf("%s ",*(weeks+i));//weeks指向一维数组
    }
    printf("\n");
}

函数指针:
指向函数的指针(函数经过编译后,在内存中会占据一块内存空间,该空间有一段首地址,指针变量可以存储这个地址。存储这个地址的指针变量就是函数指针。
定义:
数据类型 (*指针名) (参数列表)
例如:

double (*funcptr)(double,double)

注意:
(*funcptr)就是函数指针,用某个名字代替它,就是函数声明,定义时括号不能省略,否则就变成函数声明。funcptr指针变量指向参数为双double型且返回类型为都不了的函数。
例子:

double add(double a,double b)
{
    return a+b;
}

void test_fuc_p()
{
    double sum=0;
    double (*func)(double,double);//定义函数指针
    func =add;
    sum = func(1.245,2.323);//通过指针调用函数
    printf("sum ==%.3f\n",sum);
}

函数指针数组:
数组元素为函数指针,指向不同函数。
例子:

double add(double a,double b)
{
    return a+b;
}
double sub(double a,double b)
{
    return a-b;
}
double mul(double a,double b)
{
    return a*b;
}
double divi(double a,double b)
{
    return a/b;
}

void test_func_array()
{
    int x,y;
    printf("please input two numbers\n");
    scanf("%d %d",&x,&y);
     //函数指针,并把四个函数的首地址赋给数组初始值
    double (*func[4])(double,double)={add,sub,mul,divi};
    //指针数组指向多个字符串
    char *op[4]={"sum","sub","multi","divide"};
    for(int i=0;i<4;i++)
    {
        printf("%8s:%8.2f\n",*(op+i),func[i](x,y));
    }
}

输出:

please input two numbers
8 9
     sum:   17.00
     sub:   -1.00
   multi:   72.00
  divide:    0.89

命令行参数:

int main(int argc,char *argv[])

argc: argument count 表示参数个数
argv: argument vector 字符串指针数组,元素为每个参数的首地址。
argv[0]:代表程序名
argv[1]:参数1
argv[2]: 参数2
如果参数个数为N,则argv的长度为N+1。
参数内容为字符串格式
如果某个参数字符串中含有空格,需要使用“”括起来。

例子:

void test_argv_2(int argc,char *argv[])
{
    int max;
    if(argc<=1)
    {
        printf("Please use as follow:  max number 1 2 3 4 5...");
        exit(0);
    }
    printf("argc:%d\n",argc);
    for(int i=0;i<argc;i++)
    {
        printf("argument %d is %s\n",i,argv[i]);
    }
    max = atoi(argv[2]);
    //atoi :stdlib库函数,将字符串数字转换为数字,同理还有atof(double),atol(long),atoll(long long)
    for(int i=2;i<argc;i++)
    {
        if(argv[i]>max)
        {
            max=atoi(argv[i]);
        }
    }
    printf("max number:%d\n",max);

}

输出:

E:\lesson7\bin\Debug>lesson7.exe "max number" 12 34 56 31 100 321 1 4 10000
argc:11
argument 0 is lesson7.exe
argument 1 is max number
argument 2 is 12
argument 3 is 34
argument 4 is 56
argument 5 is 31
argument 6 is 100
argument 7 is 321
argument 8 is 1
argument 9 is 4
argument 10 is 10000
max number:10000

函数名,字符串名,数组名表示的都是代码快或者数据块的首地址,程序编译链接后,名字消失,取而代之的是他们的相应地址。

指针就是地址,指针变量就是存储内存地址的变量

结构体

C99 和C11 为结构体提供制定初始化器。

struct Student student={.name="bob"};

只有在定义了该结构体类型的变量才会为该结构体变量分配内存空间。
访问成员:
1,成员运算符: . (*指针变量名).成员名或者 结构变量.成员

struct Sample {
    char a;
    char c[11];
    short int n;
    short int y;
    float f;
    int  b;

};
struct Sample sa;
sa.a='a';
sa.b=4;

2,结构类型指针: -> 指针变量名->成员名

struct Sample {
    char a;
    char c[11];
    short int n;
    short int y;
    float f;
    int  b;

};
struct Sample *p;
p->a='a';
p->b=4;

关于更多结构体指针使用:
http://c.biancheng.net/view/246.html

结构体中的字符数组赋值方式:
1,在声明结构体变量时,指定字符数组赋初值

    struct Sample
    {
        char a;
         char c[10];
        short int n;
        short int y;
        float f;
        int  b;

    };
    void test_struct()
    {
    	struct Sample p={.c="aaaaaaaaa"};
    }

2,使用strcpy进行赋值

   struct Sample
    {
        char a;
         char c[10];
        short int n;
        short int y;
        float f;
        int  b;

    };
    void test_struct()
    {
    	struct Sample p;
    	strcpy(p.c,"aaaaaaaaa");
    	memset(p.c,'a',sizeof(p.c));// 最后的‘\0‘也被覆盖。
    }

在上面例子中:

结构体内存对齐方式:
规则:

内存对齐有以下原则
第一条:第一个成员的首地址为0
第二条:每个成员的首地址是自身大小的整数倍
第二条补充:以4字节对齐为例,如果自身大小大于4字节,都以4字节整数倍为基准对齐。小于4字节,则以自身长度对齐,比如char 为1,可以被任何整数整除,short 为2,地址必须为偶数。
第三条:如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,也就是max(成员变量大小)
第四条:最后以结构总体对齐,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
第四条补充:以4字节对齐为例,取结构体中最大成员类型倍数,如果超过4字节,都以4字节整数倍为基准对齐。(其中这一条还有个名字叫:“补齐”,补齐的目的就是多个结构变量挨着摆放的时候也满足对齐的要求。)

例子:

#include 

#pragma pack(4)
struct Sample {
    char a;
    char c[11];
    short int n;
    short int y;
    float f;
    int  b;

};
typedef  struct Test {
    char a[18];
   
    double b;
    char c;
    short e;
    struct Sample Sa;
    int d;
 
} MM;
void test_struct() {
    struct Sample p;
    strcpy(p.c,"aaaaaaaaaa");
    memset(p.c,'b',sizeof(p.c));
    p.a='A';
    p.n = 1;
    p.y = 2;
    p.f=2.3;
    p.b=2;
    memset(&p,0x99,sizeof(p));
    printf("size of sample is %d\n",sizeof(p));
    printf("a %x\n",&p.a);
    printf("c  %x\n",&p.c);
    printf("n  %x\n",&p.n);
    printf("y  %x\n",&p.y);
    printf("f  %x\n",&p.f);
    printf("b  %x\n",&p.b);

}
void my_test() {
    MM p;
    MM *t = &p;

    memset(&p.a,0x11,sizeof(t->a));
    memset(&p.b,0x22,sizeof(p.b));
    memset(&p.c,0x33,sizeof(t->c));
    memset(&p.d,0x44,sizeof(p.d));
    memset(&p.e,0x55,sizeof(p.e));
    p.Sa.a='A';
    strcpy(p.Sa.c,"aaaaaaaaaa");
    p.Sa.n=2;
    p.Sa.y = 3;
    p.Sa.f=4;
    p.Sa.b=5;
    memset(&p,0xAA,sizeof(p));
    memset(&p.Sa,0x99,sizeof(p.Sa));
    printf("size of struct Test is %d\n ",sizeof(*t));

}

#pragma pack()



输出:

size of struct Test is 60
 size of sample is 24
a 60fed8
c  60fed9
n  60fee4
y  60fee6
f  60fee8
b  60feec

Process returned 0 (0x0)   execution time : 0.063 s
Press any key to continue.

内存分布情况:
C语言从入门到项目实战学习笔记_第3张图片C语言从入门到项目实战学习笔记_第4张图片
参考:https://blog.csdn.net/cyousui/article/details/17655051
https://blog.csdn.net/qq_42195954/article/details/90116940

在使用结构体时,建议使用typedef 给结构体定义一个单词表示类型名的形式。

typedef struct Student
{
    char name[20];
    int score;
    int classsocre;
    char leader;
    char west;
    int papes;
}student;

链表:

单链表创建,插入,删除,排序:
https://www.jianshu.com/p/9db2db13626c
链表节点的在定义变量之后,必须使用malloc为其分配内存空间,否则会出现段错误。

单链表的创建,遍历,增加,删除,排序例子:
创建:

Node * create_list(int len)
{
    Node *pHead,*pTail;
    pHead=NULL;
    pTail = NULL;

    for(int i =0;i<len;i++)
    {
        Node *p;
        p =(Node*)malloc(sizeof(Node));
        p->data = (i+2)*10;
        p->next=NULL;
        if(i==0)
        {
            pHead=p;
            pTail=p;
        }
        else
        {
            pTail->next= p;
            pTail = p;
        }

    }
    return pHead;
}

遍历打印:

void print_list(Node *p1)
{
    Node *p2;
     printf("head:%#x\n",p1);
    for(p2 = p1;p2!=NULL;p2 = p2->next)
    {

        printf("%d---->%#x    ",p2->data,p2->next);
    }
    printf("\n");
}

插入:

Node * insert_list(Node *head,int pos,int val)
{
    Node *p;
    Node *tail =head;
    int i = 1;
    p =(Node *) malloc(sizeof(Node));
    p->data = val;

    if(pos ==1)
    {
        head=p;
        head->next=tail;
    }
    else
    {
        while((tail!=NULL) &&(i<pos-1) )
        {
            if(tail->next==NULL  && (i+1<pos))
            {
                printf("List total length: %d, can not insert data at position: %d\n",i,pos);
                free(p);
                return head;
            }
            tail = tail->next;
            i++;
        }
            p->next=tail->next;
            tail->next= p;
    }
    return head;

}

删除:

Node * delete_list(Node *head,int pos)
{
    Node *tail = head;
    Node *p;
    int i=1;
    if(pos ==1)
    {
        head=tail->next;
        free(tail);
        tail=NULL;// must do
    }
    else
    {
            while(tail!=NULL  && (i<pos-1))
        {
            i++;
            tail= tail->next;
            if(tail->next==NULL  && (i+1<=pos))
            {
                printf("List total length: %d, can not delete  position: %d\n",i,pos);

                return head;
            }

        }
        p= tail->next;
        tail->next=p->next;
        free(p);
        p=NULL;//must do

    }
    return head;

}

测试样本:

    Node *pHead= create_list(4);
    print_list(pHead);
    pHead= insert_list(pHead,2,222);
    print_list(pHead);
    pHead= insert_list(pHead,1,111);
    print_list(pHead);
    pHead= insert_list(pHead,7,777);
    print_list(pHead);
    pHead= insert_list(pHead,9,999);
    pHead=delete_list(pHead,7);
    print_list(pHead);
    pHead=delete_list(pHead,1);
    print_list(pHead);
    pHead=delete_list(pHead,2);
    print_list(pHead);
    pHead=delete_list(pHead,5);

输出:

head:0x6e0d30
20---->0x6e0d50    30---->0x6e0d60    40---->0x6e0d70    50---->0
head:0x6e0d30
20---->0x6e0d80    222---->0x6e0d50    30---->0x6e0d60    40---->0x6e0d70    50---->0
head:0x6e0d90
111---->0x6e0d30    20---->0x6e0d80    222---->0x6e0d50    30---->0x6e0d60    40---->0x6e0d70    50---->0
head:0x6e0d90
111---->0x6e0d30    20---->0x6e0d80    222---->0x6e0d50    30---->0x6e0d60    40---->0x6e0d70    50---->0x6e0da0    777---->0
List total length: 7, can not insert data at position: 9
head:0x6e0d90
111---->0x6e0d30    20---->0x6e0d80    222---->0x6e0d50    30---->0x6e0d60    40---->0x6e0d70    50---->0
head:0x6e0d30
20---->0x6e0d80    222---->0x6e0d50    30---->0x6e0d60    40---->0x6e0d70    50---->0
head:0x6e0d30
20---->0x6e0d50    30---->0x6e0d60    40---->0x6e0d70    50---->0
List total length: 4, can not delete position: 5

联合:
也称为共用体,声明与结构体十分类似,联合成员都共用一块内存单元,但是同一时刻只允许一个成员使用。联合很方便的可以通过不同方式使用同一内存单元。
声明:
union [名字] {成员声明列表}[变量名列表】;
例子:

typedef union data {
    int i;
    double x;
    char str[16];//联合中长度最大成员,所有sizeof(Data)==16
} Data;

void test_union() {
    Data var;
    printf("sizeof data is %d\n",sizeof(Data));

    var.i=5;
    for(int i=15; i>=0; i--) {
        printf("%02x ",(unsigned char)var.str[i]);
    }
    printf("\n");
    var.x = 1.25;
    for(int i=15; i>=0; i--) {
        printf("%02X ",(unsigned char) var.str[i]);
    }
    printf("\n");
    printf("%d\n",var.i);
    strcpy(var.str,"hello");
    for(int i=15; i>=0; i--) {
        printf("%02X ",(unsigned char)var.str[i]);
    }
    printf("\n");
}

输出:

sizeof data is 16
00 40 1f 6e 00 60 ff 80 00 40 1f 10 00 00 00 05
00 40 1F 6E 00 60 FF 80 3F F4 00 00 00 00 00 00
0
00 40 1F 6E 00 60 FF 80 3F F4 00 6F 6C 6C 65 68

Process returned 0 (0x0)   execution time : 0.063 s
Press any key to continue.

联合和结构体区别:

都有多个不同数据类型成员组成,但在同一时刻,联合只存放一个被选中的的成员的,而结构体所有成员都在。
对联合不同成员赋值,将会覆盖其他成员的值,原来的成员就不存在了,而对结构体的不同成员赋值,互相没有影响。

位字段:
结构或者联合的成员变量,也可以用指定特定数量的位来组成的整数变量。
声明:
类型[成员变量]:宽度
如果声明了一个无名称的位字段,就无法获取它,只能用于进行填充对齐。
宽度值必须为整数,非复,小于等于指定类型的位宽。
例子:

struct date
{
    int month:4;
    int day:5;
    int year:22;
    _Bool isDST:1;

};

void test_bit()
{
    struct date birthday={12,3,2019};
    struct  date d;
    d.month=2;
    d.day=14;
    d.year=2020;
    d.isDST=0;
    printf("%02d-%02d-%04d\n",d.month,d.day,d.year);

}

在上面结构体中成员变量只占用了一个字节,month中占用了4个bit,其他同理,但结构体占用了4个8个字节(补齐),32位系统占用4个,64位系统占用8个字节。

可以初始花列表方式初始化
也可以声明一个变量或者指针,使用 . 或者->引用变量
位字段不占据可寻址的内存空间,因此无法对位字段采用(&)取址运算符。

文件操作

当使用了标准I/O函数库时(stdio.h),系统自动设置缓冲区,通过数据流的方式读写文件,当进行文件读取时,不会直接对磁盘进行读取,而是先打开数据流,将磁盘上的文件信息复制到数据流,程序在从缓冲区读取数据。写入文件时,并不会直接写入磁盘,先写入缓冲区,只有缓冲区已满或者关闭文件时,才把数据写入磁盘。
C语言默认的文件类型时文件类型。
在C语言中,通常使用字符数组保存路径,并用转义字符""进行路径转义“\“。
文件指针:

FILE *指针变量;

FILE 时在stdio.h中的结构体。
文件位置指针:

rewind(FILE *),文件位置指针调到文件开始。

文件打开:

FILE * fopen(const char*path,const char * mode)
出错,就返回NULL;


FILE *fp = fopen("E:\\test.txt","r");
if (fp==NULL)
{
	printf("can not open file");
	return 0;
}

模式:
C语言从入门到项目实战学习笔记_第5张图片文件操作方式及意义
C语言从入门到项目实战学习笔记_第6张图片文件关闭:

fclose(FILE *)

关闭文件指针指向的文件。

文件读写操作:

字符:fgetc(FILE *),fputc(char,FILE *)操作一个字符,出错返回EOF。这两个函数适合操作较小的数文件。

字符串:fgets(char * ,int size,FILE *),fputs(char *,FILE
*)。读取size-1个字符放到数组或者指针中,字符串后面添加‘\0’。出错,返回NULL。

格式化读写:fscanf() fprintf()

数据块读写:fread() fwrite()

判断文件是否到结尾:feof(FILE *),可用于二进制文件,也可用于文本文件,到达末尾值为1,否则为0。方便判断当前文件是否结束。ferror(FILE *)检测读写是否出错,返回值为0,表示未出错,否则出错。

有关文件位置指针的函数:rewind(FILE *)将文件位置指针调节到文件首。
fseek(FILE *,long offset,int base),将文件指针移到相对基址偏移offset大小处。

base取值:
C语言从入门到项目实战学习笔记_第7张图片

fseek(fp,128L,0) //当从文件开始向后偏移128个字节
fseek(fp,-20L,1)//从当前位置向前偏移20个字节

fseek()函数一般用于二进制文件,在文本文件中需要转换,往往计算会出错。

long ftell(FILE *)获取文件当前读/写的位置,相对于文件开头的偏移。正常返回长整数,出错返回-1。

例子:
字符例子:

void test_file()
{
        FILE *fpw;
    char ch;
    char name[20];
    printf("Please input your file path and name:\n");
    scanf("%s",name);
    fpw = fopen(name,"w+");
    if(fpw==NULL)
    {
        printf("Open file to write failed\n");
        exit(0);
    }
    printf("Please input content to write:\n");
    while((ch = getchar())!='#')
    {
        fputc(ch,fpw);
    }
    rewind(fpw);
    while(!feof(fpw))
    {
        putchar(fgetc(fpw));
    }
    fclose(fpw);

}

字符串操作例子:

void test_fs()
{
    FILE *fp;
    char str[50];
    char name[]="e:\\lesson9\\bin\\Debug\\text.txt";
    puts("Please input a string");
    gets(str);
    fp =fopen(name,"a");
    if(fp==NULL)
    {
        puts("Open file failed");
        exit(0);
    }
    fputs(str,fp);
    fclose(fp);
    fp=fopen(name,"r");
    if(fp==NULL)
    {
        puts("Error");
        exit(0);

    }
    while(fgets(str,sizeof(str),fp))
    {
        puts(str);
    }
    fclose(fp);
}

例子:把小写字符转化为大写字母

#include 
#include 

int main(int argc,char * argv[]) {
    if(argc!=2) {
        printf("Usage: xx.exe filename\n");
        exit(1);

    }
    FILE *fp_r;
    FILE *fp_w;
    char ch;
    fp_r=fopen(argv[1],"r");
    fp_w=fopen("result.txt","w+");
    do {
        ch = fgetc(fp_r);
        if((ch>='a')&&(ch<='z')) {
            ch = ch-32;
        }
        fputc(ch,fp_w);
    } while(ch!=EOF);
    fclose(fp_r);
    fclose((fp_w));
    printf("Saved to result.txt!\n");

    return 0;
}


进阶提高篇

待更新

项目篇

待更新

问题及困难及下一步考虑

内存对齐。及结构体大小和内存分配?
二维数组地址理解?
指针和数组,字符串的表示关系?
结构体指针变量在内存中如何索引成员的?
变量,指针变量,指针,一维数组,二维数组,字符数组,结构体内存中表示形式,画图。
指向结构体变量的指针:
http://c.biancheng.net/view/246.html
推荐:
C和leetcode

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