• 数组做函数参数的退化: 退化为一个指针
    • 结论: 把数组的内存首地址和数组的有效长度传给被调用函数
    • 实参和形参的数据类型本质不一样, 形参中的数组, 编译器会把它当成指针处理
  • 形参写在函数上, 和写在函数内是一样的, 只不过是具有对外的属性

数据类型剖析

数组b[]

  • b代表数组首元素的地址
  • &b代表是整个数组的地址

数据类型的本质

  • 固定大小内存块的别名
  • b &b 数组数据类型(定义一个数组类型, 数组指针, 数组类型和数组指针类型的关系)

数据别名

typedef struct Man
{
    char name[64];
    int age;
}
Man;

CPP可以不重命名, c要重命名

void

  • 如果函数没有参数, 应该声明其参数为void

    int function(void)
    {
      return 1;
    }
  • void指针的意义

    • 只有相同类型的指针, 才可以相互赋值

    • void* 指针

    • 左值: 用于接收任意类型的指针

    • 右值: 赋值给其他类型时需要强制类型转换

    int *p1 = NULL;
    char *p2 = (char *)malloc(sizeif(char) *20)
    • 不存在void类型的变量. C语言没有定义void究竟是多大内存
  • 直接赋值

  • 间接赋值, == 直接通过内存

socketclient

//scoketclient_h
#ifndef _SOCKETCLIENT_H
#endif _SOCKETCLIENT_H

#ifdef __cplusplus
extern "C"{
#endif
//第一套API
// socket客户端环境初始化
int socketclient_init(void **handle); //4

//socket客户端报文发送
int socketclient_send(void *handle, unsigned char *buf, int buflen);

//socket客户端报文接受
int socketclient_send(void *handle, unsigned char *buf, int buflen);

//scoket客户端环境释放
int socketclient_destory(void *handle);

//第2套API
// socket客户端环境初始化
int socketclient_init(void **handle); //4

//socket客户端报文发送
int socketclient_send(void *handle, unsigned char *buf, int buflen);

//socket客户端报文接受
int socketclient_send(void *handle, unsigned char **buf, int buflen);

//scoket客户端环境释放
int socketclient_destory(void **handle);    
}

内存四区

  • 栈区

    • 由编译器自动分配释放, 存放函数的参数值, 局部变量的值等
    • 一般debug开口向下. release 开口向上
    • 不管栈开口向上向下, buff + 1 永远向上
  • 堆区

    • 一般由程序员分配释放( 动态内存申请与释放), 若程序员不释放, 程序结束时可能由操作系统回收
  • 全局区

    • 全局变量和静态变量的存储是放在一块的, 初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域, 该区域在程序结束后由操作系统释放
    • 常量区: 字符串常量和其他常量的存储位置, 程序结束后由操作系统释放
  • 代码区

    • 存放函数体的二进制代码
  • 指针指向谁, 就把谁的地址赋给指针

    • 编译器会优化全局的值, 指向相同值的指针指向相同.
  • 指针变量和他指向的内存空间变量是两个不同的概念

  • 理解指针的关键是在内存

  • 嵌套函数

    • fb在栈上分配的内存, 不能被fa和main函数使用
    • fb中malloc的内存(堆), 可以被main和fa函数使用
    • fb中全局分配的内存, 可以被main和fa使用
  • 每个main和那些子函数一个栈区

指针

  1. 指针是一种数据类型

    • 4个字节
    • *操作内存
      • *p在等号左, 赋值 , 给内存赋值
      • *p在等号右, 取值, 从内存取值
    • 指针变量和他指向的内存块是两个不同的概念
    • 指针是一种数据类型, 是指它指向的内存空间的数据类型
  2. 野指针

    • 指针变量和它所指向的内存空间变量是两个不同的概念

    • 释放了指针所指的内存空间, 但是指针变量本身没有重置成null

    • free(*p)
      p = NULL
  3. 间接赋值

    1. 定义一个变量实参
    2. 建立关联, 把实参取地址传给形参
    3. 形参去间接修改实参的值
  4. 字符串

    \0代表字符数串的结束标志

    //两种拷贝
    for (; *from!= '\0';)
    {
       *to++ = *from++;    
    }
    *to = '\0';
    return ;
    
    while ((*to = *from) != '\0')
    {
       from++;
       to ++;
    }
    
    while ((*to++ = *from++) != '\0')
    {
       ;
    }
    
    while ((*to++ = *from++))
    {
       ;
    }

    6.1 字符串strstr_while模型

    strstr(str, str2) 就是search, 返回第一次出现的位置

    char

  5. 辅助指针变量

    ​ 就是在函数里, 如果要改变形参, 先保存一下形参, 用辅助指针变量去操作, 这样就不会因为操作形参指针, 丢失了传入的值

  6. C语言函数表

  7. 两头堵模型

    1. 一个往左走, 一个往右走
  8. 逆序

    1. int lenstr = strlen(buf)
      p1 = buf
      p2 = buf + lenstr -1
      while (p1 < p2){
      char c = *p1;
      *p1 = *p2;
      *p2 = c;
      ++p1;
      --p2;
      }
    2. void inverse02(char *p)
      {
        if (p == NULL)
        {
            return ;
        }
        if (*p == '\0')
        {
            return;
        }
        inverse02(p+1);
        printf("%c", *p);
      
      }
    3. char g_buf[100]
      void inverse02(char *p)
      {
        if (p == NULL)
        {
            return ;
        }
        if (*p == '\0')
        {
            return;
        }
        inverse02(p+1);
        strncat(g_buf, p, 1);
      }

二级指针

  • 指针的指针, 二级指针. 二级指针定义时候要写两个*

    char *p1 = NULL;
    char **p2 = &p1;
  • 二级指针做输入

    //数组中每一个元素都是指针, 指针数组
    char *myArray[] = {"aaaa", "bbbbb", "ccccc", "1111"}
    
    //二维数组
    
    //第三种, 手工二维内存
    char **p2 = NULL;
    int num = 5;
    p2 = (char **)malloc(sizeof(char *)* num);
    for (i = 0; i<5; i++)
    {
      p2[i] = (char *)malloc(sizeof(char) * 100);
    }

数组

概念: 数组是相同类型变量的有序集合, 连续的一大片内存空间

初始化:

//可以指定长度
int a[10] = {1, 2};
//也可以不指定长度
int b[] = {1, 2};
int c[200] = {0};  // 编译的时候就已经确定所有的值为零

memset(c, 0, sizeof(c)); //显式重置内存块
//c是数组首元素的地址 c+1 步长4个字节
// &c是整个数组的地址 &c+1 步长200*4

类型的本质: 固定大小内存块的别名

//定义数组类型
typedef int (MyArrayType)[5];
MyArrayType myArray;  //int myArray[5] 
  • 数组类型指针

    数组指针用于指向一个数组

    char *MyArray[] = {"111", "222", "aaa"}; // 指针数组
    //数组指针, 指向一个数组
    MyArrayType *pArray;  //定义一个指针变量, 这个指针变量指向一个数组
    {
      int a;
      int *p = NULL;
      p = &a;
    }
    {
      int myArray2[5];
      for (i=0, i<5; i++)
      {
        myArray2[i] = i + 1;
      }
      pArray = &myArray2;
      for (i=0; i<5; i++)
      {
          printf("%d", (*pArray)[i]);
      }
    }
    //定义声明一个数组指针类型
    typedef int (*PArrayType)[5]
    PArrayType pArray;    //告诉编译器给我分配一个指针变量
    
    int c[5];
    pArray = &c;
    
    for (i=0; i<5; i++)
    {
      (*pArray)[i] = i + 1;
    }
    
    //定义数组指针变量的第三种方法
    //前两种方法都是通过类型定义变量, 比较麻烦
    int (pMyArray)[5];  //直接定义一个指向数组的 数组指针变量
    int c[5];
    pMyArray = &c;

多维数组

int a[3][5];
//a 多维数组名 代表
//a+1 的步长 是20个字节, 5*4
//多维数组名的本质就是一个数组指针, 每次后跳的长度就是一维数组的长度, 步长就是一维数组的长度
// (a+i) 代表第i行的首地址, 二级指针
//*(a+i) 代表1级指针, 代表第i行首元素的地址
// *(a+i) + j ====> & a[i][j] 

多维数组做函数参数退化的原因:

void printArray(int a[3][5])
{}
void printArray(int a[][5])
{}
void printArray(int (*b)[5])
{}
// 多维数组做函数参数, 退化过程
//为什么存在退化
  1. C语言只会机械式的值拷贝的方式传递参数(实参把值传给形参)

  2. 二维数组参数同样存在退化问题

    void f(int a[5]) ===> void f(int a[]); ====>void f(int *a);

    void g(int a[3] [3]) ====> void g(int a[] [3]) ====> void g(int (*a)[3]);

    二维数组参数中第一维的参数可以省略

  3. 等价关系

    数组参数 等效的指针参数
    一维数组 char a[30] 指针 char *
    指针数组 char *a[30] 指针的指针 char **a
    二维数组 char a[10] [30] 数组的指针 char (*a)[30]

指针数组的应用场景

int main(int argc, char* argv[], char**env)
    //argv 就是命令行参数
    //env 就是环境变量
  • 自我结束能力

在操作系统的框架之下

结构体

基本操作

#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

//定义一个结构体类型

// 定义了一个数据类型, 固定大小内存块别名, 还没有分配内存
struct Teacher
{
    char name[64];
    int age;
    int id;
}

void main()
{
    struct Teacher t1; // 告诉c编译器给我分配内存

    printf("hello...\n");
    system("pause");
    return
}

类型的重定义

#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

//定义一个结构体类型

// 定义了一个数据类型, 固定大小内存块别名, 还没有分配内存
typedef struct Teacher
{
    char name[64];
    int age;
    int id;
}Teacher;

void main()
{
    Teacher t1; // 告诉c编译器给我分配内存

    printf("hello...\n");
    system("pause");
    return
}

定义类型的同时定义变量

struct Student
{
    char name[64];
    int age;
}s1, s2;

// 匿名类型
struct
{
    char name[64];
    int age;
}s3, s4

初始化变量的三种方法

struct Student
{
    char name[64];
    int age;
}s5 = {"name", 21};
//匿名
struct
{
    char name[64];
    int age;
}s6 = {"name", 21};
// 第三种
Teacher t3 = {"aaaa", 31, 01};

结构体变量的引用

Teacher t1;
t1.age = 31;  //t1. 操作符
// . 是寻址操作, 是计算age 相对于t1 大变量的偏移量 ==> 计算在CPU中进行
// 没有操作内存

//通过指针的方式 操作内存空间
{
    Teacher *p = NULL;
    p = &t2;
    printf(p->age);
    // -> 也是寻址, 计算age 相对于大变量t2的偏移量
    //表达式 p->m 等效于(*p).m

结构体赋值 编译器行为研究

void main()
{
    Teacher t1 = {"aaaa", 32, 1};
    Teacher t2;

    t2 = t1;
}

结构体直接做参数, 浅拷贝; 需要用结构体地址, 用指针操作, 才是深拷贝

结构体做函数参数

void printTeacher(Teancher *array, int num)
{}

int createTeacher(Teacher **pT, int num)
{
    Teacher * tmp = NULL;
    tmp = (Teacher *)malloc(sizeof(Teacher) * num);
    *pT = tmp
}

结构体嵌套二级指针

//嵌套一级指针
typedef struct Teacher
{
    char name[64];
    char *alisname;
    int age;
    int id;
}Teacher;

二级指针

int createTeacher02(Teacher **p, int num)
{
    Teacher *tmp = NULL;
    tmp = (Teacher *)malloc(sizeof(Teacher) * num);
}

把第一种内存模型的第二中内存模型数据拷贝到第三种内存模型中

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdio.h>

int sort(char **myp /*in*/, int num1, char(*myp2)[30], int num2, char ***myp3, int *num3)
{
    int i -0, j=0. k=0;
    char **p3 = NULL;
    p3 = (char **)malloc( (num1 + num2) * sizeof(char *)); //里面装的是指针
    if (p3 == NULL)
    {
        return -1;
    }
    //根据字符串长度分配内存
    for (i=0; i < num1; i++)
    {
        tmplen = strlen(myp1[i]) + 1;
        p3[i] = (char *)malloc(tmplen * sizeof(char));
        if (p3[i] == NULL)
        {
            return -2;
        }
        strcpy(p3[i], myp1[i]);
    }

    for (j=0; j<num2; i++, j++)
    {
        teplen = strlen(myp2[j]) + 1;
        p3[i] = (char *)malloc(tmplen * sizeof(char));
        if (p3[i] == NULL)
        {
            return -3;
        }
        strcpy(p3[i], myp2[j]);
    }
    tmplen = num1 + num2;
    //排序
    for (i=0; i<*tmplen; i++)
    {
        for (j=i+1; j < tmplenl; j++)
        {
            if (strcmp(p3[i], p3[j]) > 0) 
            {
                tmpP = p3[i];
                p3[i] = p3[j];
                p3[j] = tmpP;
            }
        }
    }
    //间接赋值
    *num3 = tmplen;
    *myp3 = p3;

    return 0;
}

void sortFree(char **p, int len)
{
    if (p == NULL)
    {
        return;
    }
    for (i=0; i<len; i++)
    {
        free(p[i]);
    }
    free(p);
}

//把二级指针所指向的二维内存释放, 同时间接修改了实参的值
void sortFree01(char ***myp, int len)
{
    int i = 0;
    char **p = NULL;
    if (myp == NULL)
    {
        return;
    }
    p = *myp;   //还原二级指针
    if (p == NULL)
    {
        return;
    }
    for (i=0; i<len; i++)
    {
        free(p[i]);
    }
    free(p);
    //myp 是 实参的地址
    *myp = NULL; //间接赋值是指针存在的最大意义
}

void main()
{
    char *p1[] = {"111", "222", "3333"};
    char buf2[10][30] = {"aaa", "bbb", "ccc"};
    char **p3 = NULL;
    int len1, len2, len3;

    len1 = sizeof(p1)/sizeof(*p1);
    len2 = 3;

    ret = sort(p1, len1, buf2, len2, &p3, &len3);
    if (ret != 0)
    {
        printf("func_sort() err: %d \n", ret);
        return ret;
    }
    for (i=0; i<len3; i++)
    {
        printf("%s \n", p2[i]);
    }
    print("hello...\n");
    system("pause");
    return;
}

浅拷贝: 编译器的等号操作, 只会吧指针变量的值拷贝, 但不会把指针变量所指的内存空间拷贝

结构体的高级话题

// 一旦结构体定义下来, 则结构体中的成员, 内存布局, 就定下来了了
// 可以通过age 内存地址, 去求 大的结构体的内存地址 
typedef struct AdvTeacher
{
    char name[64];
    int age;
    int p;
    char *pname2;
}AdvTeacher;

void main()
{
    AdvTeacher t1;
    AdvTeacher *p = NULL;
    // strcpy(p1, "ddd");会down掉
    p -1 ; //请问这句话能编译通过吗? 能运行通过吗?
    //能编译过, 也能运行
    //这句话是在CPU里面运行的, 没有访问内存, 放在寄存器里面
    p - p; //这个也是木有问题的
    //偏移量
    {
        int offsize = (int)&(p->age);
        printf("%d \n", offsize);
    }
    int offsize = (int)&(((AdvTeacher *)0)->age)
    // 这就是偏移量, 从一个小孔可以看见外面的世界

    return;
}

文件操作

文件API

  1. 读写文件
    1. fgetc fputc 按照字符读写文件
    2. fgets fputs 按照行读写文件(读写配置文件)
    3. fread fwrite 按照块读写文件(大数据块迁移)

按照字符读写文件

void main()
{
    FILE *fp = NULL;
    char *filename = "c:\\1.txt";
    char *filename2 = "c:/2.txt"; //统一的用45度的
    fp = fopen(filename2. "r+");
    if (fp == NULL)
    {
        printf("func fopen() err: %d \n");
        return;
    }
    printf("open succeed");
    fclose(fp);

}

按照行的方式读写文件

  1. c 函数库会一行一行拷贝数据, 到buf指针所指的内存空间中, 并且变成C风格的字符串
  2. 把\n也拷贝到我们的buf中
  3. 内存打包(把内存首地址+ 内存的长度)

按照块的方式操作文件

直接把内存数据写入到文件中, fwrite()

参数

//_Count 是写多少次

//_Str: 从内存块的开始

//_Size //内存打包地址

//_File: 写入到文件指针所指向的文件中

返回值

写入次数, 判断有没有写满磁盘

接口封装和设计思路分析

  1. 分清出需求
  2. 分析出功能的输入和输出
  3. 实现代码
写配置文件
int WriteCfgItem(const char _filename/*in*/, const char *key/*in*/, const char *value/*in*/)
读配置文件

接口要求紧, 模块要求松 ===> 设计理念

const MaxLine = 2048;
int GetCfgItem(char *pFileName /*in*/, char *pKey /*in*/, char * pValue/*in out*/, int *pValueLen/*out*/)
{
    int ret = 0;
    FILE *fp = NULL;
    char lineBuf[MaxLine];

    fp = fopen(pfileName, "r");
    if (fp == NUll)
    {
        ret = -1;
        return ret;
    }
    while (!feof(fp))
    {
        memset(lineBuf, 0, sizeif(lineBuf));
        fgets(lineBuf, MaxLine, fp);

        pTmp = strchr(lineBuf, "=");
        if (pTmp == NULL)
        {
            continue;
        }
        pTmp = strstr(lineBuf, pKey);
        if (pTmp == NULL) //判断所在行是不是有key
        {
            continue;
        }
        pTmp = pTmp + strlen(pKey);     //mykey1 = myvalue1111==> = myvalue11111
    }
}

把文件指针从0位置开始, 移动到文件末尾

fseek(fp, 0L, SEEK_END);
length = ftell(fp);

fseek(fp, 0L, SEEK_SET);

文件加密

数据加密的过程, 是一块一块加密的, 其实是一个数据搬运的过程

指针的用法杂项

int  getContentLen01(const char *filename, char **buf. int * len)
{
    //在被调用函数里分配内存
    char *tmp = NULL;
    tmp = (char *)malloc(100*sizeof(char));
    if (tmp == NULL)
    {
        return -1;
    }
    strcpy(tmp, "aaaaa");
    *len = 10; 
    *buf = tmp; //间接赋值
    return 0;
}

int  getContentLen02(const char *filename, char *buf. int * len)
{
    //在被调用函数里分配内存
    char *tmp = NULL;
    tmp = (char *)malloc(100*sizeof(char));
    if (buf == NULL)
    {
        *len = 10; //第一次调用求长度
    }
    else
    {
        strncpy(tmp, "aaaaaaaaaaaa", 10);
        *len = 10;
    }
    strcpy(tmp, "aaaaa");
    *len = 10; 
    *buf = tmp; //间接赋值
    return 0;
}

void main()
{
    const *filename = "c:/1.txt";    
    char *p = NULL;
    int len = 0;
    getContentLen01(filename, &p, &len);

    if (p != NULL)
    {
        free(p);
        p = NULL;
    }
    //第一次调用
    getContentLen02(filename, NULL, &len);

    p = (char *)malloc(len);
    if (p != NULL)
    {
        p = NULL;
    }
    //第二次调用
    getContentLen02(filename, NULL, &len);
    return
}

一般应禁用malloc/new

指针函数参数 内存分配方式 主调函数实参 被调函数形参 备注
01 一级指针(in) 分配 使用 一般应禁用
分配 使用 常用
int showbuf(char p);

int showArray(int array, int iNum)

02 一级指针(out) 使用 结果传出 常用
int getLen(char pFileName, int pfileLen);
03 二级指针(in) 分配 使用 一般应禁用
分配 使用 常用
int main(int arc, char *arg[]); 指针数组
int showMatrix(int [3] [4], int iLine); 二维字符串数组
04 二级指针(out) 使用 分配 常用, 但不建议使用转化成02
int getData(char data, int dataLen);
int getData_Free(void
data);
int getData_Free(void
data); 避免野指针
05 三级指针(out) 使用 分配 不常用
int getFileAllLine(char * content, int * pLine);
int getFileAllLine_Free(char **content, int pLine);

动态库

.lib 资源模式文件, 描述 .dll

.dll 动态库(函数二进制码集合, 里面有函数的函数体) 动态库是有规范的(win/ linux)

//日志功能集成
IT_LOG(__FILE__, __LINE__, LogLevel[2], ret, "func cli() begin: %d \n", ret);

要注意导入动态库的顺序, 防止环境变化导致的导入错误

动态库的内存释放问题: 动态库里面申请的内存, 必须在其中释放,

内存泄露

检测工具

mtrace

memwatch

memwatch

加入到工程下的方法:

  1. copy .h .c 到工程源码下, 然后添加到工程

  2. 增加宏-------MEMWATCH; MW_STDIO;

    右键项目--属性--配置属性--常规--预处理器定义--加入进去

链表

  1. 结构体中套一个结构体--可以

  2. 结构体中套一个结构体的指针--可以

  3. 结构体中套一个自己类型的结构体--不可以

    在自己类型大小还没有确定的情况下, 引用自己类型的元素是不正确的

    结构体不能嵌套定义(分配不了内存)

  4. 结构体中套一个指向自己类型的指针

静态链表

固定个数

每个节点都在临时区分配,

typedef struct Teacher
{
    int data; 
    struct Teacher *next;
}Teacher;

void main()
{
    Teacher t1;
    t1.data = 1;
    t2.data = 2;
    t3.data = 3;

    t1.next = &t2;
    t2.next= &t3;
    t3.next= NULL;

    return
}

动态链表

本站文章基于国际协议BY-NA-SA 4.0协议共享;如未特殊说明,本站文章皆为原创文章,请规范转载。


0

‌ 如果够出色,却不能出头,至少也做到没第二个我。