给移动开发者的C快速入门

0. 搭建运行环境

0.1 Windows

参考文末前两篇文章即可,编辑器使用vs code即可,比较轻量级

  1. 下载安装vs code
  2. 下载安装MinGW
    官网:https://www.mingw-w64.org/downloads/
    下载地址:https://sourceforge.net/projects/mingw/
  3. 配置环境变量
    解压MinGW到安装目录,Path添加到xxx/bin,命令行运行:where gcc检查配置效果

1. 变量

1.1 基本数据类型

直接上代码:

// c没有bool类型,但是c++有,注意区别
void testVariant()
{
    int p1 = 1;
    long p2 = 2L;
    float p3 = 3.0;
    double p4 = 4.0;
    char p5 = 'a';
    short p6 = 100;

    printf("value =  %d, sizeof p1 = %d\n", p1, sizeof(p1));  //value =  1, sizeof p1 = 4
    printf("value =  %ld, sizeof p2 = %d\n", p2, sizeof(p2)); //value =  2, sizeof p2 = 4
    printf("value =  %f, sizeof p3 = %d\n", p3, sizeof(p3));  //value =  3.000000, sizeof p3 = 4
    printf("value =  %lf, sizeof p4 = %d\n", p4, sizeof(p4)); //value =  4.000000, sizeof p4 = 8
    printf("value =  %c, sizeof p5 = %d\n", p5, sizeof(p5));  //value =  a, sizeof p5 = 1
    printf("value =  %d, sizeof p6 = %d\n", p6, sizeof(p6));  //value =  100, sizeof p6 = 2
}

1.2 指针

1.2.1 一级指针

要点:

  • 取址符& 和 取值符*
  • 指针要带数据类型的原因:1)确定如何取值;2)确定单位偏移量(针对数组指针);

示例:取址符& 和 取值符*

void testPointer()
{
    int i = 100;
    int *pi = &i;
    int *pi2 = pi;
    // 指针的基本概念
    printf("pi = %#x, value = %d\n", pi, *pi);  // pi = 0x61fe0c, value = 100
    printf("pi2 = %#x, value = %d\n", pi2, *pi2);   // pi2 = 0x61fe0c, value = 100,指针变量可以用指针变量接收
    printf("pi3 = %p, value = %#x\n", &pi2, *(&pi2));  // pi3 = 0x61fe08, value = 0x61fe14,指针变量也是变量,所以也有地址

    // 核心知识点:指针操作变量值
    *pi = 200;
    printf("i = %d, *pi2 = %d\n", i, *pi2);   // i = 200, *pi2 = 200
}

示例:交换变量值

void swap(int *x, int *y)
{
    int temp = *x;
    *x = *y;
    *y = temp;
}

示例:变量内存地址是不会发生改变的,下面这段交换值的函数将不生效

void swap_num_ptr(int* x, int* y)
{
    int* tmp = x;
    x = y;
    y = tmp;
}

void swap_num_wrong(int x, int y)
{
    int temp = x;
    x = y;
    y = temp;
}
1.2.2 多级指针
    int num = 100;
    int *p_num = #
    int **pp_num = &p_num;
    printf("num = %d",**pp_num);
1.2.3 函数指针

格式:返回类型(指针变量名)(形参类型)=函数名
函数指针可以作为接口使用
示例:函数指针作为形参类型

int add(int x, int y)
{
    return x + y;
}

int minus(int x, int y)
{
    return x - y;
}

int operate(int(method)(int, int), int x, int y)
{
    return method(x, y);
}

int main()
{
    int x = 3, y = 4;
    int z = operate(add, x, y);
    printf("z = %d\n", z);
    z = operate(minus, x, y);
    printf("z = %d\n", z);
    return 0;
}

示例:函数指针变量的定义与赋值

int main()
{
    int(*method)(int, int) = add;     //
    return 0;
}

示例:与数组类似,函数名其实就是函数指针

int main()
{
   printf("add = %p, &add = %p",add,&add);  //add = 00000000004017CE, &add = 00000000004017CE
    return 0;
}
1.2.4 常量指针与指针常量

要点:常量指针不可修改变量值,指针常量不可修改指针值

// 常量指针(const在*之前)
int num = 11;
int num2 = 12;
const int* p_num=#
p_num = &num2;   //允许对常量指针地址重新赋值
// *p_num = 13;   //报错,不允许修改常量指针的值

// 指针常量(const在*之后)
int * const p_num2 = #
// p_num2 = &num2; //报错,指针常量不允许地址重新赋值
*p_num2 = 13;   //允许修改指针常量的值

1.3 数组

要点:

  • arr=&arr=&arr[0],都是数组第一个元素的地址
  • 数组的内存地址是连续的,所以arr++是有意义的,表示arr的下个元素的地址
void testArray()
{
    int arr[] = {0, 2, 3};
    printf("arr = %p\n", arr);
    printf("&arr = %p\n", &arr);
    printf("&arr[0] = %p\n", &arr[0]);

    // 第一种遍历数组方式
    for (int i = 0; i < 3; i++)
    {
        printf("num = %d\n", arr[i]);
    }

    // 第二种遍历数组的方式
    int *parr = arr;  //这里为了演示arr的指针类型是int*才专门开辟一个指针变量接收,实际开发直接用arr即可
    for (int i = 0; i < 3; i++)
    {
        printf("num = %d\n", *parr++);
        printf("num = %d\n", *(parr+i));
        printf("num = %d\n", parr[i]);
    }
}

思考2:如何获取数组长度?
int arr[] = {0, 2, 3}; int len = sizeof(arr)/sizeof(int); //可以
int* arr = (int*)malloc(sizeof(int)*3); int len = sizeof(arr)/sizeof(int); //不可以,不能用指针变量获取数组长度

1.4 结构体

c的结构体跟Java的类(实体类)非常相似,区别是结构体构造方法只有默认的一个,无法像Java那样重载。支持内部结构体。

1.4.1 结构体的定义
// 普通结构体
struct Student
{
    char name[10];  //ISO C++ forbids converting a string constant to 'char*'
    int age;
    double score;
};

// 定义同时初始化两个实例:两个实例的属性初始值默认是null或者0
struct Teacher
{
    char name[10];
    char subject[16];
    Student students[1];
}t_wang={"王老师","math",{{"csfchh", 10, 100.0}}},t_li;   


int main()
{
    struct Student stu = {"csfchh", 10, 100.0};
    printf("name = %s, age = %d, score = %ld\n", stu.name, stu.age, stu.score);
    strcpy(stu.name,"hahha");  //无法直接修改,就是用字符串赋值
    stu.age++;
    printf("name = %s, age = %d, score = %ld\n", stu.name, stu.age, stu.score);

    t_wang.name;
    t_wang.subject;
    t_wang.students[0].name;
    return 0;
}

思考:结构体的内存大小如何计算?
字节补齐法则:当结构体中含有基本数据类型时,以最大的数据类型大小为单位,结构体总大小为这个单位的最小公倍数;如果结构体中不含基本数据类型?
例如Student=8*3=24
Teacher=56,怎么算?

1.4.2 结构体的使用
1.4.2.1 结构体创建

使用typedef

typedef Student STU;   //将Student的别名定为STU,这样就可以用STU替代Student使用,用于简化代码,有些编译器不能直接使用结构体,需跟上struct关键词,用别名就可以绕开这个问题
typedef Teacher* TC, TEA;  //别名可以取多个;对指针取别名就可以少敲一个*号

// 使用typedef避免编译器报错示例
// 普通结构体
typedef struct Student
{
    char name[10]; //ISO C++ forbids converting a string constant to 'char*'
    int age;//属性没赋值不能直接读
    double score;
} Student;

创建结构体

// 创建结构体变量(一般不这么用)
struct Student stu = {"csfchh", 10, 100.0};

// 创建结构体指针(主流用法)
Student* p_stu = &stu;   //c++,这种用法依赖于结构体变量
Student* p_stu2 = new Stuent();  //c++
Student* p_stu3 = (Student*)malloc(sizeof(Student));
if(p_stu3) free(p_stu3);

// 创建结构体数组
Student stus[3] = {{"aa",10,60.0}, {"bb",11,56.0},{}};
stu[2] = {"cc",10,60.0};

Student* stus2 = (Student*)malloc(sizeof(Student)*3);
strcpy(stus2->name,"aa");  //操作第一个结构体
stus2->age = 10;
stus2->score = 85;
stus2++;  //后移一个单元长度,指向下一个结构体女
strcpy(stus2->name,"bb");  //操作第二个结构体
stus2->age = 11;
stus2->score = 56.0;
1.4.2.2 结构体访问

两种访问接头体属性的方法:使用结构体变量和使用结构体指针

int main()
{
	struct Student stu = {"csfchh", 10, 100.0};
    printf("name = %s, age = %d, score = %ld\n", stu.name, stu.age, stu.score);
    // 使用结构体变量访问
    strcpy(stu.name,"hahha");  //无法直接修改,就是用字符串赋值
    stu.age++;
    printf("name = %s, age = %d, score = %ld\n", stu.name, stu.age, stu.score);

    // 使用指针访问结构体属性
    Student *p_stu = &stu;
    p_stu->age = 11;
    return 0;
}

1.4 联合体

定义方式结构体一样,但是特性完全不同,联合体内的属性只有第一个访问的才有效,其他的均会被抛弃。
联合体的大小计算非常简单,等于属性最大值(还是要基本数据类型整除进行对齐)

typedef union Activity
{
    int sleep;
    double eat;
} Activity;


void main()
{
    Activity act = {1, 2.0};
    printf("sleep = %d, eat = %d\n", act.sleep,act.eat);  //sleep = 1, eat = 1
}

1.5 枚举

枚举就是一个int,可以直接给枚举赋值一个整数,枚举默认第一个值是0

enum ContentType{
    TEXT,IMAGE=1,OTHER
};

2. 函数

2.0 导包

首先说明一下导包的问题:

  • 导系统包:
    eg. #include <stdlib.h>
  • 导第三方包:
    eg. #include “jni.h”
    eg. #include “test.c”

#include导包实际上是将被导文件在编译时直接复制到目标文件中,这就存在循环引用问题,因此需要处理这个问题:

// 在被导的.h文件中增加下面的说明
#ifndef _included_pkgname_classname
#define _included_pkgname_classname

......   
#endif 

_included_pkgname_classname是.h文件的自定义标识,需要保证唯一性,一般推荐的命名格式:
stdio.h -> STDIO_H

2.1 字符串<string.h>

2.1.1 字符串的表达
  1. char[]: eg. char str[]={‘h’,‘e’,‘l’,‘l’,‘o’,‘\0’}; //最后一个字符固定,必须是\0;
  2. char*: eg. char* str = “hello”;
    区别是char[]是动态的,而char*是静态的。
2.1.2 长度strlen

字符串长度用int strlen(char* str)函数获取。

思考1:strlen如何获取字符串长度?
由于字符串以\0作为固定的结尾,那么只需要一个while循环判断当前数组元素是否是\0即可。

2.1.3 转换ato、strto

主要是ato和strto的系列方法:

void testStringTrans(){
    char* str="12";
    int v = atoi(str);   //转成int,如果转换失败会返回0不会抛异常
    str = "12.5";
    float v2 = atof(str);   //转换成float,如果转换失败返回0.000000
    double v3 = strtod(str,NULL);  //转换成float,如果转换失败返回0.000000
}
2.1.4 添加strcpy/strcat
  • strcpy:从头开始覆盖
  • strcat:从尾巴添加
2.1.5 截取

可以使用strcpy间接实现或者for循环自己拷贝

2.1.6 替换

需要考虑以下情况:

  • 目标不存在;
  • 字符串长度变化;
  • 处理拼接逻辑;
  • 存在多个目标;
2.1.7 查找

char* pos = strstr(char* str, char* goal); //返回goal首次出现的地址指针,否则返回null

2.1.8 比较strcmp

int rc = strcmp(char* str1, char* str2) 区分大小写
int rc = strcmpi(char* str1, char* str2) 不区分大小写
int rc = strncmp(char* str1, char* str2, int count) 区分大小写,比较前n个

rc等于0表示相等,rc大于0表示大于,rc小于0表示小于。

2.1.9 大小写转换

使用for循环遍历每个char,然后使用tolower(char c) 和 toupper(char c)进行转化

2.2 时间<time.h>

2.3 数学计算<math.h>

2.4 文件操作<stdio.h>

接口说明示例
FILE* fopen(char* filepath,char* mode)打开文件
void fclose(FILE* file)关闭文件
int fread(int[] buffer, int stepsize, int stepcount, FILE* file)读取文件以buffer为单位进行读
int fwrite(int[] buffer, int stepsize, int stepcount, FILE* file)写到文件以buffer为单位进行写
int fgetc(FILE* file)读取一个字符以单个字符为单位
int fputc(int c, FILE* file)写出一个字符以单个字符为单位
2.4.1 读写文件
void testIO()
{
    char *srcFilePath = "..\\lake.png";
    char *dstFilePath = "..\\lake_copy.png";
    FILE *src = fopen(srcFilePath, "rb");
    FILE *dst = fopen(dstFilePath, "wb");
    if (!src||!dst)
    {
        printf("can not open files");
        return;
    }
    int SIZE=512;
    int buffer[SIZE];
    int len = -1;
    int step = sizeof(int);
    while ((len = fread(buffer, step, SIZE, src)) != 0)
    {
        printf("len = %d\n",len);
        fwrite(buffer, step, len, dst);
    }
    fclose(src);
    fclose(dst);
    printf("copy finished");
}
2.4.2 获取文件大小
void testGetFileSize()
{
    const char *srcFilePath = "..\\lake.png";
    FILE *file = fopen(srcFilePath, "rb");
    if (!file)
    {
        printf("can not open files");
        return;
    }
    // 指针移动到文件末尾
    fseek(file, 0, SEEK_END);
    // 计算指针的偏移量就是文件大小
    long walksize = ftell(file);
    printf("file size is = %ld\n",(walksize/1024));
}
2.4.3 二进制文件简单加密
void testFileEncodeAndDecode()
{
    int MASK = 56;
    const char *srcFilePath = "..\\lake.png";
    const char *encodeFilePath = "..\\lake_encode.png";
    const char *decodeFilePath = "..\\lake_decode.png";
    FILE *src = fopen(srcFilePath, "rb");
    FILE *encode = fopen(encodeFilePath, "wb"); //如果不存在则会创建该文件

    if (!src || !encode)
    {
        printf("can not open files");
        return;
    }

    int SIZE = 512;
    int buffer[SIZE];
    int step = sizeof(int);
    int len = -1;
    // 这里是用fread+fwrite读写buffer
    while ((len = fread(buffer, step, SIZE, src)) != 0)
    {
        buffer[0] ^= MASK;   //核心原理是两次异或后数字还原
        fwrite(buffer, step, len, encode);
    }
    //还可以使用fgetc+fputc读写单个字符,效率相对较低
    // int c;
    // while((c=fgetc(src))!=EOF){
	//	fputc(c^MAST, encode);
	//}
    // 关闭文件
    fclose(src);
    fclose(encode);
    len = -1;

    encode = fopen(encodeFilePath, "rb");
    FILE *decode = fopen(decodeFilePath, "wb");
    while ((len = fread(buffer, step, SIZE, encode)) != 0)
    {
        buffer[0] ^= MASK;   //核心原理是两次异或后数字还原
        fwrite(buffer, step, len, decode);
    }
    // 关闭文件
    fclose(encode);
    fclose(decode);
}
2.4.4 文件切割与合并
// 获取文件大小
long getFileSize(char *fileName)
{
    FILE *file = fopen(fileName, "rb");  //read binary
    if (!file)
        return 0;
    // 指针移动到文件末尾
    fseek(file, 0, SEEK_END);
    // 计算指针的偏移量就是文件大小
    long file_size = ftell(file);
    fclose(file);
    return file_size;
}
// 合并文件
void joinFiles(char **fileNames, int fileCount)
{
    const char *joinName = "..\\lake_join.png";
    FILE *file = fopen(joinName, "ab");  //append binary
    if (!file)
    {
        printf("can not write to joint file\n");
        return;
    }

    
    FILE *temp;
    int c = 0;
    int SIZE=512;
    int buffer[SIZE];
    // int len;
    int step=sizeof(int);
    for (int i = 0; i < fileCount; i++)
    {
        printf("filename[%d] = %s\n", i, fileNames[i]);
        temp = fopen(fileNames[i], "rb");  
        if (!temp)
        {
            printf("can not read from segment\n");
            return;
        }
        // while ((len=fread(buffer,step,SIZE,temp))!=0)
        // {
        //     printf("len = %d\n", len);
        //     fwrite(buffer, step, len, file);
        // }
        // len=0;
        while ((c = fgetc(temp)) != -1)
        {
            printf("c = %d\n", c);
            fputc(c, file);
        }
        fclose(temp);
    }
    fclose(file);
}

// 文件切割
int splitFile(char *fileName, char **fileNames,int fileCount)
{
    long file_size = getFileSize(fileName);
    if (file_size <= 0){
        return 0;
    }
    FILE *file = fopen(fileName, "rb");
    const long SPLIT_SIZE = file_size / fileCount;
    FILE *temp;
    long start = 0;
    long end = 0;
    int c = 0;
    for (int i = 0; i < fileCount; i++)
    {
        printf("fileNames[%d] = %s\n", i, fileNames[i]);
        // 计算分片的大小
        start = i * SPLIT_SIZE;
        if (i == fileCount - 1)
        {
            end = file_size;
        }
        else
        {
            end = (i + 1) * SPLIT_SIZE;
        }
        // 获取分片进行读写
        temp = fopen(fileNames[i], "wb");   //write binary
        if (!temp)
        {
            printf("can not write to segement\n");
            return 0;
        }
        for (int j = start; j < end; j++)
        {
            fputc(fgetc(file), temp);
        }

        fclose(temp);
    }
    c = 0;
    fclose(file);
    return 1;
}

// 测试方法
void testFileSplitAndJoin()
{
    char *fileName = "..\\lake.png";
    int SPLIT_COUNT = 3;
    char **fileNames = (char **)malloc(sizeof(char *) * SPLIT_COUNT);
    for (int i = 0; i < SPLIT_COUNT; i++)
    {
        fileNames[i] = (char *)malloc(sizeof(char) * 30);
        sprintf(fileNames[i], "..\\lake_%d.png", i);
    }
    // 先进行分片
    if (splitFile(fileName, fileNames, SPLIT_COUNT))
    {
        // 再进行合并
        joinFiles(fileNames, SPLIT_COUNT);
    }

    for (int i = 0; i < SPLIT_COUNT; i++)
        free(fileNames[i]);
    free(fileNames);
}

3. 内存管理

先看一个例子引出内存概念:

void test(){
	int aar[10*1024*1024];   //直接申明,属于静态内存开辟
}

上面的代码开辟的内存空间为:(10 * 1024 * 1024 * 4+4) 个bit;

3.1 四区模型

内存名称说明内存开辟方式
存放函数的参数,局部变量值最大内存空间2M,开辟内存方式为静态开辟(编译器自动分配)
存放程序员动态申请的变量(malloc、new等)最大内存空间为可用内存的80%,开辟内存方式为动态开辟,需要手动释放(free、delete)
数据区常量区(字符串常量等)、全局区/静态区(存放全局变量和静态变量)由操作系统负责开辟和释放
代码区存放二进制代码操作系统管理
双击运行程序的执行流程

3.2 动态内存管理

3.2.1 申请和释放<stdlib.h>

void testMalloc()
{
    // 申请40M堆内存
    int *mem = (int *)malloc(10 * 1024 * 1024 * sizeof(int));
    // 释放堆内存
    free(mem);
}

3.2.2 堆内存的使用

// 演示如何往动态内存写数据
void testMallocUsage()
{
    int *mem = (int *)malloc(sizeof(int) * 5);
    int i = 0;
    for (; i < 5; i++) mem[i] = i;
    free(mem);
}

3.2.3 内存再申请

允许对已申请的内存块调整大小,示例如下:

// 演示重新申请内存以修改动态内存大小
void testRealloc()
{
    // 开始申请5个
    int *mem = (int *)alloc(sizeof(int) * 5);
    // 发现5个,不够用,再申请5个,共计10个
    mem = realloc(mem, sizeof(int) * 5);
    if (mem)//相当于if(mem!=NULL)
    {
        free(mem);
        mem = NULL;
    }
}
  • alloc函数是从栈上分配内存无需释放;
  • malloc是从堆内存分配内存需要释放;

4. 参考文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值