JNI开发Android 知识音视频开发

重拾c语言基础-记录笔记

2018-10-31  本文已影响10人  未见哥哥

自从出来工作之后就没在用过 c 了,为了更加深入的学习一些底层的东西,不得已重新来过一遍基础的知识点。

C的基本数据类型

signed&unsigned

原码、反码、补码

基本数据类型的长度

JAVA

C

示例代码

#include <stdio.h>

int main(int argc, const char * argv[]) {
    
    //基本数据类型的长度
    //sizeof返回的 unsign long 无符号的long类型
    printf("%lu\n",sizeof(int));//4
    printf("%lu\n", sizeof(short));//2
    
    printf("%lu\n", sizeof(long));//在32位操作系统中long的长度4个字节,64位操作系统中long的长度为8个字节
    printf("%lu\n", sizeof(float));//4
    printf("%lu\n", sizeof(double));//8
    printf("%lu\n", sizeof(char));//1
    
    

    printf("%lu\n", sizeof(long long));//8

    //以下两种声明的方式,我觉得是没有必要的,因为要表示2个长度的数据类型,直接使用 short 就好的,要表8位长度的数据使用 long 就行了。
    printf("%lu\n",sizeof(short int));//2
    printf("%lu\n",sizeof(long int));//8

    
    //定义一个无符号的变量a,最大值42亿
    unsigned int a = 4294967295;
    
   
    
    //最高位表示符号位,只能表示无符号的一半大小,默认定义的整形类型就是有符号位的
    signed int b = 2100000000;
    
    //如果使用无符号来表示-1的话,它表示的是无符号的最大值4294967295。
    unsigned int c = -1;
    
    
    
    printf("%u\n",a);
    
    printf("%d\n",b);
    
    printf("%u\n",c);
    
    //char只能存放一个字符,并且使用''表示
    char ch = 'b';
    
    printf("%c",ch);
    
    
    return 0;
}

指针

内存地址

指针变量

#include<stdio.h>
#include<stdlib.h>
int main(){
    
    int i;
    
    i = 10;
    
    //打印变量的地址,注意占位符使用%#x
    //printf("%#x\n",&i);
    
    int b = 3;
    
    //定义一个int类型的指针,存放是int类型变量的地址。
    //这是一个一级指针
    int *p = &b;
    
    
    //定义一个二级指针,存放的是指针的地址。
    int **q = &p;
    
    printf("p指针所存放内存地址对应的值是%d\n",*p);
    
    printf("q指针所存放内存地址对应的值是%d\n",**q);
    
    
    //指针的常见错误
    //常见错误1:野指针
    int *pp ;
    //定义了,但没有赋值的指针,称为野指针。
    //printf("pp的内存地址是%#x\n",&pp);
    int a = 3;
    //在这里直接将a变量赋值给一个指针变量的指向的内存单元。这是不行的,因为这是野指针。
    //*pp = a;
    
    //正确做法应该是:给指针pp赋值,让其指向一个有效的内存地址。
    pp = &a;
    
    printf("%d\n",*pp);
    //常见错误2:指针类型错误
    
    double d = 3.14;
    //给指针变量赋值错了数据类型的地址。
    //pp = &d;
    
    return 0;
}

指针的长度

多级指针&多级指针的访问

多级指针的访问

引用传递

#include<stdio.h>

//引用传递
void swap(int *a ,int *b){
    
    int temp = *a;
    
    *a = *b;
    
    *b = temp;
    
}
int main() {
    
    int a = 10;
    
    int b = 20;
    
    swap(&a,&b);
    
    printf("a = %d\n",a);
    printf("b = %d\n",b);
    return 0;
}

主函数获取子函数的变量的内存地址

#include<stdio.h>
//错误版本
//void function(int* p){
//
//    int i = 3;
//    p = &i;
//    printf("i 的地址是%p\n",&i);
//}


//i 的地址是0x7fff4fda8994
//mainp指针存放的地址是:0x7fff4fda89e8
//int main() {
//
//
//    //定义一个可以接收地址的指针
//    int* mainp;
//
//    function(mainp);
//
//    printf("mainp指针存放的地址是:%p\n",mainp);
//    return 0 ;
//}

//正确版本
void function(int** p){
    
    int i = 3;
    *p = &i;
    printf("i 的地址是%p\n",&i);
}


//i 的地址是0x7fff5607a994
//mainp指针存放的地址是:0x7fff5607a994
int main() {
    
    
    //定义一个可以接收地址的指针
    int* mainp;
    
    function(&mainp);
    
    printf("mainp指针存放的地址是:%p\n",mainp);
    return 0 ;
}

常见错误

数组

#include <stdio.h>


int main(){
    
    char c[] = "hello";
    
    printf("第8个元素的值是%c\n",c[8]);//c语言的数组是不检测越界。
    
    //数组每一个元素的地址都是连续的。
    printf("c[0]的地址%#x\n",&c[0]);//c[0]的地址0xeef9f98a
    printf("c[1]的地址%#x\n",&c[1]);//c[1]的地址0xeef9f98b
    printf("c[2]的地址%#x\n",&c[2]);//c[2]的地址0xeef9f98c
    printf("c[3]的地址%#x\n",&c[3]);//c[3]的地址0xeef9f98d
    printf("c[4]的地址%#x\n",&c[4]);//c[4]的地址0xeef9f98e
    
    //打印数组变量名字的地址
    printf("c数组变量名字的地址是%#x\n",&c);//c数组变量名字的地址是0xeef9f98a
    
    int data[] = {1,2,3,4,5};
    
    //数组每一个元素的地址都是连续的。整型数据每一个元素之间相差4个字节
    printf("data[0]的地址%#x\n",&data[0]);//data[0]的地址0xe538f970
    printf("data[1]的地址%#x\n",&data[1]);//data[1]的地址0xe538f974
    printf("data[2]的地址%#x\n",&data[2]);//data[2]的地址0xe538f978
    printf("data[3]的地址%#x\n",&data[3]);//data[3]的地址0xe538f97c
    printf("data[4]的地址%#x\n",&data[4]);//data[4]的地址0xe538f980
    
    //打印数组变量名字的地址
    printf("data数组变量名字的地址是%#x\n",&data);//data数组变量名字的地址是0xe538f970
    //打印的数组变量和打印数组变量的地址是一样的,都是表示数组第一个元素的地址。
    printf("data数组变量是%#x\n",data);//data数组变量名字的地址是0xe538f970
    
    //数组变量名字的地址就是数组第一个元素的地址
    
    
    //内存地址是可以进行加减运算的。
    //定义一个 cp 指针,存放数组cy首元素的地址
    //指针+1表示内存地址位移一位
    char* cp = &c;
    
    printf("第1个元素的值%c\n",*(cp+0));//h
    printf("第2个元素的值%c\n",*(cp+1));//e
    printf("第3个元素的值%c\n",*(cp+2));//l
    printf("第4个元素的值%c\n",*(cp+3));//l
    printf("第5个元素的值%c\n",*(cp+4));//o
    
    int* datap = &data;
    
    printf("第1个元素的值%d\n",*(datap+0));//1
    printf("第2个元素的值%d\n",*(datap+1));//2
    printf("第3个元素的值%d\n",*(datap+2));//3
    printf("第4个元素的值%d\n",*(datap+3));//4
    printf("第5个元素的值%d\n",*(datap+4));//5
    
}

数组与指针的关系

如何用指针表示数组

int a[] = {1,2,3,4};
    
//使用指针p存放数组a首个元素的地址。
int *p = a;

如何通过指针访问数组元素

//使用指针修改数组的值
*(p+0) = 10;

示例代码

#include<stdio.h>

int main() {
    
    int a[] = {1,2,3,4};
    
    //使用指针p存放数组a首个元素的地址。
    int *p = a;
    
    printf("数组变量:%p\n",a);//0x7ffeede7d960
    printf("指针地址:%p\n",p);//0x7ffeede7d960
    
    //使用指针修改数组的值
    *(p+0) = 10;
    
    printf("指针访问数组元素:%d\n",*(p+0));
    
    return 0;
}

常见错误

#include<stdio.h>
int main() {
    int * p;
    int a = 1;
    double d = 4.0;
    
    
    //printf("%p\n",&p);//指针没有指向不能轻易使用,它是一个野指针
    
    p = &a;
    
    printf("*p = %d\n",*p);//*p = 1
    
    //指针指向地址存放的类型必须和声明指针的类型一致
    //p = & d;
    
    return 0;
}

堆与栈

malloc

malloc 的图解

malloc 的图解

示例代码

#include <stdio.h>
#include <stdlib.h>


int main() {
    
    int len ;
    
    printf("请输入数组的长度\n");
    
    scanf("%d",&len);
    
    //malloc动态分配内存,p指针指向首字节地址
    int *p = (int*)malloc(sizeof(int)*len);
    

    for(int i = 0;i<len;i++){
        *(p+i) = i*10;
    }
    
    
    for(int i = 0;i<len;i++){
        printf("第%d个数据是%d\n",(i+1),p[i]);
    }
    
    printf("p指针变量的地址:%p\n",p);
    printf("第1个元素的值:%d\n",*(p));
    
    
    
    int a = p[1];
    printf("malloc 分配的内存空间指针相当于一个数组,%d\n",a);
    
    //释放在堆中动态分配的内存空间;
    //free()释放的是指针指向的内存。指针变量依然存在,只有程序结束时才被销毁。不过现在指针指向的内容是未定义的垃圾,所以现在指针又成为了野指针。
    //void free(void *FirstByte):该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。
    free(p);
    
    printf("p指针变量的地址:%p\n",p);
    printf("第1个元素的值:%d\n",*(p));
    
    for(int i = 0;i<len;i++){
        printf("第%d个数据是%d\n",(i+1),p[i]);
    }
    
    /**
     
     在 Mac OS 系统:
     
        free(p) 之后 *P 没有变化,p的地址没有发生变化
     
     
     在 Linux 系统:
     
        free(p) 之后 *p 的值不再是 free 之前的值
     */
     
           
    return 0;
}

realloc

重新分配主存

示例代码

#include <stdio.h>
#include <stdlib.h>


int main() {
    
    int len ;
    
    printf("请输入数组的长度\n");
    
    scanf("%d",&len);
    
    //malloc动态分配内存,p指针指向首字节地址
    int *p = (int*)malloc(sizeof(int)*len);
    

    for(int i = 0;i<len;i++){
        *(p+i) = (i+1)*10;
    }
    
    
    for(int i = 0;i<len;i++){
        printf("第%d个数据是%d\n",(i+1),p[i]);
    }
    
    for(int i = 0;i<len;i++){
        printf("指针p[%d]的地址是:%p\n",(i+1),(p+i));
    }
    
    int size = 0;
    printf("请输入需要补充的值\n");
    scanf("%d",&size);
    
    
    
    p = (int *)realloc(p,size+len);
    
    for(int i = 0;i<len+size;i++){
        *(p+i) = (i+1)*10;
    }
    
    for(int i = 0;i<len+size;i++){
        printf("第%d个数据是%d\n",(i+1),p[i]);
    }
    
    
    for(int i = 0;i<len+size;i++){
        printf("指针p[%d]的地址是:%p\n",(i+1),(p+i));
    }
    
    
    
    free(p);
    
    
    
    return 0;
}

/*
 
 realloc函数的使用
 
 函数名: realloc
 功  能: 重新分配主存
 用  法: void *realloc(void *ptr, unsigned newsize);
 
 在指针 ptr 后面分配新的内存空间。
 
 请输入数组的长度
 4
 第1个数据是10
 第2个数据是20
 第3个数据是30
 第4个数据是40
 指针p[1]的地址是:0x7fd4e7402ab0
 指针p[2]的地址是:0x7fd4e7402ab4
 指针p[3]的地址是:0x7fd4e7402ab8
 指针p[4]的地址是:0x7fd4e7402abc
 请输入需要补充的值
 3
 第1个数据是10
 第2个数据是20
 第3个数据是30
 第4个数据是40
 第5个数据是50
 第6个数据是60
 第7个数据是70
 指针p[1]的地址是:0x7fd4e7402ab0
 指针p[2]的地址是:0x7fd4e7402ab4
 指针p[3]的地址是:0x7fd4e7402ab8
 指针p[4]的地址是:0x7fd4e7402abc
 指针p[5]的地址是:0x7fd4e7402ac0
 指针p[6]的地址是:0x7fd4e7402ac4
 指针p[7]的地址是:0x7fd4e7402ac8
 */

自定义类型

结构体

//1.定义一个结构体
struct Student {
    int age;
    char *name;
    char sex;
    //eatp表示函数指针的名字
    void(*eatp)();
};
//方式1 
struct Student st = {        
    11,"张三",'m'
};

//方式2
//给结构体内的成员赋值
st.age = 12;
st.name = "李四";
st.sex = 'm';

结构体内指针不能定义函数,只能定义函数指针。

//给结构体指针赋值
st.eatp = eat;
//用结构体变量引用变量
 printf("年龄:%d\n名字:%s\n性别:%c\n",st.age,st.name,st.sex);
 
 //用指针变量的方式引用结构体属性
struct Student *p = &st;
printf("年龄:%d\n名字:%s\n性别:%c\n",(*(p)).age,(*(p)).name,(*(p)).sex);
#include<stdio.h>

void eat() {
    printf("学生吃饭\n");
}

struct Student {
    int age;
    char *name;
    char sex;
    //eatp表示函数指针的名字
    void(*eatp)();
};

int main(void){

    //定义一个结构体变量,并给结构体成员赋值
//    struct Student st = {
//        11,"张三",'m'
//    };
    
    
    struct Student st ;

    //给结构体内的成员赋值
    st.age = 12;
    st.name = "李四";
    st.sex = 'm';
    //给结构体指针赋值
    st.eatp = eat;
   //输出结构体成员的值

    printf("年龄:%d\n名字:%s\n性别:%c\n",st.age,st.name,st.sex);
    
    
    struct Student *p = &st;
    
    
    printf("年龄:%d\n名字:%s\n性别:%c\n",(*(p)).age,(*(p)).name,(*(p)).sex);
    
    
    printf("年龄:%d\n名字:%s\n性别:%c\n",p->age,p->name,p->sex);
    //调用结构体指针。
    p->eatp();
    return 0;
}

文件处理

示例代码

//文件的操作
#include<stdio.h>


int main(void){
    
    //定义一个文件类型的变量
    
    FILE *file = NULL;
    
    //打开文件
    //FILE *fopen(char *filename, char *type);
    //a+ 表示 append 追加 +表示如果没有这个文件就创建一个文件
    
    file = fopen("hello.txt","a+");
    
    char *str = "hello world";
    
    //int fwrite(void *ptr, int size, int nitems, FILE *stream);
    fwrite(str,1,11,file);
    
    
    //将文件指针重新指向一个流的开头
    //int rewind(FILE *stream);
    //因为文件的打开mode是 a+ 追加的方式,所以要将处理文件的指针指向一个流的开头。才能读取到数据
    rewind(file);
    char buf[1024] = {};
    //读出来
    // int fread(void *ptr, int size, int nitems, FILE *stream);
    fread(buf,1,11*2,file);
    
    printf("读出来的数据是%s\n",buf);
    
    fclose(file);
    
    return 0;
}
/*
 "r" = "rt"
 打开一个文本文件,文件必须存在,只允许读
 "r+" = "rt+"
 打开一个文本文件,文件必须存在,允许读写
 "rb"
 打开一个二进制文件,文件必须存在,只允许读
 “rb+”
 打开一个二进制文件,文件必须存在,允许读写
 "w" = “wt”
 新建一个文本文件,已存在的文件将被删除,只允许写
 "w+" = "wt+"
 新建一个文本文件,已存在的文件将被删除,允许读写
 “wb”
 新建一个二进制文件,已存在的文件将被删除,只允许写
 “wb+”
 新建一个二进制文件,已存在的文件将被删除,允许读写
 "a" = "at"
 打开或新建一个文本文件,只允许在文件末尾追写
 "a+" = "at+"
 打开或新建一个文本文件,可以读,但只允许在文件末尾追写
 “ab”
 打开或新建一个二进制文件,只允许在文件末尾追写
 “ab+”
 打开或新建一个二进制文件,可以读,但只允许在文件末尾追写
 对于文件使用方式有以下几点说明:
 1) 文件使用方式由r,w,a,t,b,+六个字符拼成,各字符的含义是:
 r(read): 只读
 w(write): 只写
 a(append): 追加
 t(text): 文本文件,可省略不写
 b(binary): 二进制文件
 */

C语言的编译器

编译命令

编译 c 代码,不同的操作系统使用的编译工具不一样,Mac OS 使用的 clang ,Linux 使用的是 gcc

gcc/clang -g -o main main.c -I 关联的头文件位置 -L 关联的库位置 -l关联的库名字 -l关联的库名字2...

编译过程

.c (预处理) -> .i 文件(编译) -> .s(汇编) -> .o(链接) -> 可执行文件

预处理

获取预处理后的.i文件

helloworld.i文件

查看这个文件可以知道,在 .c 预处理之后的 helloworld.i 文件可以看出,源码会被保留,并且会将头文件的内容写到出头文件的内容上面。

clang -c add.c -o add.out

将 add.out 编译为静态库 mylib

libtool -static -o libmylib.a add.out
clang -o main main.c -I . -L . -lmylib
./main

源代码如下

#include<stdio.h>
#include "add.h"
int main(void){  
    int result = add(1,2);
    printf("输出结果是%d\n",result);
    return 0;
}
int add(int a,int b);
#include<stdio.h>
#include "add.h"
int add(int a,int b){
    return a+b;
}

宏定义

就是将 #define 定义的内容安装字符串进行替换。

#include<stdio.h>

//宏定义是没有c语言语法规则的,因此可以不需要写;
#define R 10
#define _main int main(
//传递参数
#define N(n) n*10
//定义一个加法运算的宏
//对比一个函数来说,这样定义宏的好处是什么?
//1.它的参数的类型不限制
//2.返回值类型不限制
#define ADD(a,b) a+b
_main) {
    
    int a = R;
    
    printf("a = %d\n",a);
    
    
    int b = N(5);
    printf("b = %d\n",b);
    
    
    int c = ADD(5,6);
    printf("c = %d\n",c);
    
    
    float d = ADD(5,6.3);
    printf("d = %f\n",d);
    
    //注意:宏定义只是将字符串进行替换操作,不会考虑一些逻辑运算符关系的,需要手动控制。
    //2+3*4+5 = 2+12+5 = 19
    int e = ADD(2,3)*ADD(4,5);
    
    int f = (ADD(2,3))*(ADD(4,5));
    
    printf("e = %d\n",e);//19
    printf("f = %d\n",f);//45 正确的
    return 0;
}

类型定义 typedef

一般会是用于给自定义类型起别名,例如在jni中,就大量的使用了typedef来将c的类型起别名为java类型。

typedef int tni;

int main() {
    //在预处理后的.i文件,这里的tni并不会像宏定义一样被替换。
    tni a = 10;

    printf("a = %d\n",a);

    return 0;
}

联合体

#include<stdio.h>
//1.定义一个联合体data
union data {
    int a;
    int b;
    char c;
};

int main() {
    
    union  data _data;
    
    _data.a = 1;
    _data.b = 2;
    _data.c = 'c';
    
    printf("a = %d\n",_data.a);
    printf("b = %d\n",_data.b);
    printf("c = %c\n",_data.c);
    
    //取出地址,每一个变量存储的地址都是一样的
    //%p打印地址
    printf("a = %p\n",&_data.a);//0x7fff54c929c8
    printf("b = %p\n",&_data.b);//0x7fff54c929c8
    printf("c = %p\n",&_data.c);//0x7fff54c929c8
    
    
    //这里只会输出最后一个赋值的元素 也就是char c = 'c' 会被存储、其他两个会被 c 覆盖
    printf("a = %c\n",*(&_data.a));//99
    printf("c = %c\n",_data.c);//99
    return 0;
}

链表

静态链表

#include<stdio.h>

struct weapon {
    
    int price;
    int atk;
    
    struct weapon * next;
    
};

int main() {
    
    struct weapon a,b,c,*head;
    
    a.price = 100;
    a.atk = 101;
    
    b.price = 200;
    b.atk = 201;
    
    c.price = 300;
    c.atk = 301;
    
    
    //head指向第一个元素
    head = &a;
    
    a.next = &b;
    
    b.next = &c;
    
    //最后一个元素的next属性为空
    c.next = NULL;
    
    
    struct weapon *p;
    p = head;
    while(p!=NULL){
        printf("price = %d,atk = %d\n",p->price,p->atk);
        
        //下面两种方式都行
        //p = (*p).next;
        p = p->next;
        
    }
    return 0;
}

动态链表

动态的往链表中添加数据

#include<stdio.h>
//mac下<malloc.h>头文件找不到,具体原因未查明
#include<mm_malloc.h>

struct weapon {
    int price;
    int atk;
    struct weapon *next;
    
};


struct weapon * create() {
    struct weapon *head;//指向第一个元素
    struct weapon *p1,*p2;//p1指向当前输入的元素,p2指向上一个元素
    
    int n = 0;//表示输入的元素的次数
    
    p1 = p2 = (struct weapon*)malloc(sizeof(struct weapon));
    
    //p1->atk = 10;
    //p1指向的是一个 weapon 结构体
    //我们向往这个结构体里面的属性赋值,首先就需要取出结构体的元素的地址: &(p->price)
    

    //下面两种输入方式都可以
    scanf("%d,%d",&(p1->price),&p1->atk);
    
    head = NULL;
    
    while (p1->price!=0) {
        n++;
        
        if(n == 1){
            //第一次输入,保存到head中
            head = p1;
        }else{
            p2->next = p1;
        }
        //p2指向上一个元素(相对下一次循环来说的)
        p2 = p1;
        
        //给p1重新分配内存空间
        p1 = (struct weapon*)malloc(sizeof(struct weapon));
        scanf("%d,%d",&p1->price,&p1->atk);
    }
    
    p2->next = NULL;
    return head;
    
}

int main(){
    struct weapon * head = create();
    while (head!=NULL) {
    
        printf("price = %d,atk = %d\n",head->price,head->atk);
        
        //下面两种方式都行
        //p = (*p).next;
        head = head->next;
    }
    return 0;
}

工具

我们在使用 c 语言开发时,不可能将所有的 API都记住,因此我们应该常备 API文档在身,在 Mac平台比较好用的应该就是 Dash 了。

Dash 可以下载不同语言的 API 文档,非常方便,推荐大家使用.

image.png
上一篇下一篇

猜你喜欢

热点阅读