NDK | C 语言复习笔记

简介: NDK | C 语言复习笔记

数据类型


C 语言中数据类型分有符号和无符号,默认是有符号的。


类型 同义词 存储空间
signed char 1 字节
int signed int、signed 2 或 4 字节
short signed short、short int、signed short int 2 字节
long signed long、long int、signed long int 4 字节
long long (C99) signed long long、long long int、signed long long int 8 字节
类型 同义词 存储空间
unsigned char 1 字节
unsigned int unsigned 2 或 4 字节
unsigned short unsigned short int 2 字节
unsigned long unsigned long int 4 字节
unsigned long long (C99) unsigned long long int 8 字节


C 语言定义的 int 长度是不比 short 短,不比 long 长。具体长度取决于编译时指定的目标平台。



是「名称 - 替换文本」的映射,预处理时会将源码中出现宏名称的地方展开为指定的替换文本;


宏定义:
#define ARRAY_SIZE 100
使用宏:
double data[ARRAY_SIZE]
复制代码


带参数的宏 注意宏名称和左括号之间不能有空格,否则会变成无参数的宏。


#define DISTANCE(x, y) ((x)>=(y) ? (x)-(y) : (y)-(x))
int d = DISTANCE(1,2
复制代码

变量


变量类型 作用域 生命周期 内存区域
局部变量 函数内部 函数 栈区
全局变量 整个项目 进程 数据区
静态局部变量 函数内部 进程 数据区
静态全局变量 源文件内部 进程 数据区

函数


函数类型 作用域 存储位置
全局函数 整个项目 代码区
静态函数 源文件内部 代码区
  • 函数声明 必须放在函数调用之前,函数声明可以省略形参声明,这依赖于 C语言没有函数重载(C++有函数重载)。函数定义 不一定要放在函数调用之前。形参可以不带参数名。
  • printf 占位符


占位符 含义
%d、%i 有符号整数
%u 无符号整数,最高位当作值的一部分
%o 有符号整数(八进制)
%x 有符号整数(八进制)
%X 有符号整数(十六进制),字母按大写显示
%c 字符
%s 字符串
  • inline 内联函数: 函数体在编译时直接嵌入函数调用位置,运行时不会再执行函数调用指令,省去了函数调用的开销。内联是以代码膨胀为代价的,因为每一处内联函数的调用的位置都复制了一份代码,程序总代码量增大,程序会占用更大的内存空间。所以内联函数只适合简短的函数,另外内联函数不能直接递归调用。


inline void fun(); // 无效
void fun() {
}
--------------------------
void fun();
inline void fun() { // 有效
}
复制代码

注意:inline 需要应用在 函数定义 才有效,应用在 函数定义 是无效的。


关键词


static 关键字的作用

  • 静态存储类型: 用于修饰变量,使之成为静态局部变量或静态全局变量;
  • 静态函数: 用于修饰函数,使之成为静态函数。

const 关键字的作用

  • 修饰变量 / 形参: 变量值不能修改


const int a = 1;
a = 2; (X)// Cannot assign to variable 'a' with const-qualified type 'const int'
注意:const 和 类型 int 可以互换位置,以下两条语句是等价的。
const int a = 1;
int const a = 1;
复制代码
  • 修饰指针: const 可以配合指针变量使用,记忆方法如下:
  • 1、const 离变量名近是修饰指针变量本身,指针指向不可变;
  • 2、const 离变量名远是修饰指针指向的变量,指针指向的变量不可变;


const int * p; // 指针指向的变量不可变
int const * p; // 指针指向的变量不可变
int * const p; // 指针指向不可变
const int * const p; // 都不可变
int const * const p; // 都不可变
int a = 1;
const int * p = &a;
*p = 2; (X)// Read-only variable is not assignable
复制代码
  • 修饰函数返回值: 一般用于返回指针的函数,表示不允许修改返回指针指向的数据


const * int func(); // const 离变量名远,上面提到的 const int * p 的情况。
复制代码


const 和 define 的对比

const 和 define 有类似之处,它们都定义了一个不允许修改的值,如果只从功能上看,它们都是 “常量”。但是从实现原理上看,它们并不是常量。

  • define 是预编译指令,define 定义的宏在预处理阶段就展开为指定的文本,define 定义的宏在编译后就不存在了。
  • const 是变量修饰符,本质上是一个 只读变量,一般所谓 “const 常量”,是从功能上对 const 的描述。


extern 关键字的作用


  • 修饰变量或函数: 表示该变量或函数是在外部文件中定义的全局变量或全局函数,提示编译器在外部文件中查找。

main.c


// 如果不使用 extern 修饰,会提示找不到变量。
extern int g ;
g = 1;
extern int g = 1; (X)// 这种写法不允许:'extern' variable cannot have an initializer
复制代码


other.c


int g;
复制代码
  • extern "C" : 实现 C++ 代码调用 C 语言代码,该代码将按照 C 语言的方式进行编译链接。


// 如果处于 C++ 环境,全部使用 C 的标准
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
复制代码

volatile 关键字的作用


用于修饰变量,提醒编译器该变量随时可能发生改变,程序每次需要读取变量值的时候,都需要直接从变量地址中读取,而不应该使用寄存器中的缓存。


volatile int a;
复制代码

include 关键字的作用


#include <系统资源>
#include "项目资源"
复制代码

数组


字符串


  • 字符串本质上是以\0为结束符的字符数组,C 风格字符串自动在数组末尾增加\0结束符。


char *p = "xurui";
p[2] = '1'; 不能修改全局区域
printf("%s", p);
复制代码
  • 遍历字符串


char *str = "xurui";
while (*str) {
    ...
    str++
}
复制代码
  • 字符串转换atoi: 将字符串转换为长整型


函数原型:参数是指向字符串的指针,返回值是长整形
int atoi(const char *str)
调用函数:
#include <stdlib.h>
int result = atoi("1");
类似函数:atol、atof、strtod
复制代码

怎么判断转换成功?

  • 字符串比较strcmp: 比较两个字符串strncmp: 比较两个字符串,最多比较前 n 个字符


函数原型:参数是两个字符串指针
int strcmp(const char *str1, const char *str2)
int strncmp(const char *str1, const char *str2, size_t n)
该函数返回值如下:
- 如果返回值小于 0,则表示 str1 小于 str2。
- 如果返回值大于 0,则表示 str1 大于 str2。
- 如果返回值等于 0,则表示 str1 等于 str2。
调用函数:
#include <string.h>
int result = strcmp("1", "2")
复制代码
  • 字符查找strstr: 查找第一次出现的位置


函数原型:
char *strstr(const char *haystack, const char *needle)
调用函数:
char *text = "peng xurui";
char *subtext = "x";
char *pop = strstr(text, subtext);
printf("%s\n", pop); // 输出 xurui
int index = pop - text;
printf("索引:%d", index); // 输出 5
复制代码
  • 字符串拼接strcat: 拼接两个字符串


函数原型:把 src 字符串追加到 dest 字符串的结尾
char *strcat(char *dest, const char *src)
复制代码
  • 大小写转换tolower: 转换为小写字符toupper :转换为大写字符


函数原型:把 c 字符转换为小写字符
int tolower(int c);
调用函数:
char c = 'A';
tolower(c);
复制代码
函数原型:把 c 字符转换为大写字符
int toupper(int c);
调用函数:
char c = 'a';
tolower(c);
复制代码

指针


  • & 取地址,* 取地址中的值,%p 地址的占位符。
  • 指针存放的是内存地址,不过指针本身也有内存地址。
  • 指针的大小 与指向的地址存储的变量类型无关,只与操作系统的字长有关。sizeof(int*) 在32 位系统中指针为 4 字节,64 位系统中指针为 8 字节。
  • 野指针 是指向不可用内存地址的指针,例如指针指向超出数组范围的地址。野指针不是空指针。
  • 函数指针 是指向函数的指针。函数的地址是函数存储空间的首地址。函数指针的定义方式为:函数返回值类型 (*指针变量名) (函数参数列表);


int Func(int x); 声明一个函数
int (*p) (int x); 定义一个函数指针
p = Func; 将 Func 函数的首地址赋给指针变量 p
(*p)(9); 调用函数
(p)(9); 可以省略 *,这里是函数指针特例
复制代码
  • 行指针
  • 列指针


堆内存管理


  • malloc: 在堆上分配一块新的连续空间


函数原型:参数是分配的字节数
void *malloc(sizt_t size);
调用函数:
#include <stdlib.h>
int *p = (int *) malloc(sizeof int);
if (!p) {
    printf("申请失败");
}
复制代码
  • calloc: 在堆上分配一块新的连续空间,并初始化为0


函数原型:参数是元素个数 + 元素占用字节数
void* calloc(size_t num_elements,size_t element_size);
调用函数:
#include <stdlib.h>
int *p = (int *) calloc(5, sizeof(int));
if (!p) {
    printf("申请失败");
}
复制代码
  • realloc: 调整一块已经分配的内存空间(扩大或缩小)


函数原型:参数是原地址的指针 + 新的内存大小
void realloc(void *ptr, size_t new_size);
调用函数:
#include <stdlib.h>
int *p = (int *) malloc(20 * sizeof(int));
int *p1 = (int *) realloc(p, 21 * sizeof(int));
int *p2 = (int *) realloc(p, 2000 * sizeof(int));
printf("%x\n", p); // 输出 ce4054c0
printf("%x\n", p1); // 输出 ce4054c0
printf("%x\n", p2); // 输出 ce809800(新开辟了一块内存空间)
复制代码


如果是将分配的内存扩大,则有以下情况: 1)如果当前内存段后面有足够内存空间,则直接扩展这段内存空间,realloc() 返回原指针; 2)否则,在堆中新开辟一块内存,并释放原来的内存块,返回新内存块地址; 2)否则,分配失败返回 NULL。


  • free: 释放动态分配的内存块


函数原型:参数是内存块的指针
free(void* pointer);
调用函数:
#include <stdlib.h>
if (p) {
    free(*p);
    p = NULL; // 防止存现悬空指针
}
复制代码


1)使用 malloc,calloc,realloc 分配的内存,必须通过 free 释放; 2)不能重复释放同一个内存块,因为释放后操作系统可能会把该空间分配给其他用途。


结构体


  • 声明结构体


struct Student {
    char *name;
    int age;
    char sex;
}; // 必须加分号
复制代码
  • 初始化结构体


// 方式 1:
struct Student mark;
mark.name = "mark";
mark.age = 10;
mark.sex = '1';
// 方式 2:
struct Student mark = {"mark", 10, '1'};
复制代码

注意:结构体中的成员变量初始化为系统值,而不像 Java 会给成员变量初始化为零值。

  • 声明时初始化


struct Student {
    char name[10];
    int age;
    char sex;
} mark = {"mark", 10, '1'},
tom, amy;
复制代码
  • 结构体嵌套


声明结构体:
struct Student {
    char name[10];
    int age;
    char sex;
    struct Sport {
        char *name;
    } sport;
};
使用结构体:
struct Student mark = {"mark", 10, '1'};
struct Student tom = {"tom", 1, '1'};
mark.sport.name = "basketball";
tom.sport.name = "football";
printf("%s\n", mark.sport.name); // basketball
printf("%s\n", tom.sport.name); // football
复制代码


注意: 嵌套的 sport 结构体是独立的两个结构体,是独立的两块内存。


  • 结构体指针

结构体指针是指向结构体的指针。


struct Student *p = &mark;
printf("%s\n", p->name); // mark
复制代码

其中,->是调用指针的一级成员变量。


  • 结构体的静态开辟和动态开辟


静态开辟:栈
struct Student mark;
mark.name = "mark";
动态开辟:堆
struct Student *markP = malloc(sizeof(struct Student));
markP->name = "mark";
printf("%s\n", markP->name); // mark
复制代码
  • 结构体数组


结构体数组是元素为结构体的数组。


静态开辟:栈
struct Student student[3] = {
            {"mark", 10, '1'},
            {"tom",  1,  '1'},
            {},
};
struct Student *p = &student;
printf("%s\n", student[0].name); // mark
printf("%s\n", (p + 1)->name); // tom
动态开辟:堆
struct Student *p = malloc(sizeof(struct Student) * 3);
p->name = "mark";
(p + 1)->name = "tom";
printf("%s\n", p->name); // mark
printf("%s\n", (p + 1)->name); // tom
free(p);
复制代码
  • 结构体别名


定义别名:
typedef struct Student Student_;
typedef struct Student * pStudent;
使用别名:
Student *p = malloc(sizeof(Student) * 3);
pStudent p = malloc(sizeof(Student) * 3);
复制代码


这里需要注意 Clion 和 VS 两个编辑器的差异化:


Clion 要求使用 struct Student
struct Student *p = malloc(sizeof(struct Student) * 3);
free(p);
VS 可以省略 struct 关键字
Student *p = malloc(sizeof(Student) * 3);
free(p);
复制代码


为了避免源码在不同编译器不统一,可以定义一个相同的别名:


定义一个相同的别名
typedef struct Student Student;
使用别名:
Student *p = malloc(sizeof(Student) * 3);
复制代码

提示: 你可以在源码中看到很多结构体都定义了别名,就是为了保持代码统一。C 语言枚举也有类似的问题。


  • 匿名结构体


struct { // 匿名结构体
    char *name;
}; // 不能引用,没有任何意义
struct { // 匿名结构体
    char *name;
} a1, a2, a3;
复制代码

文件操作

  • fopen :打开文件


函数原型:使用指定 mode 打开文件,返回 FILE 指针
FILE *fopen(const char *filename, const char *mode)
模式:
1)r:读取
2)w:写入
3)a:在文件后追加
4)r+
5)w+
6)a+
使用函数:
FILE *file = fopen("文件名", "r");
if (!file) {
    // 文件打开失败
}
char buffer[10];
while (fgets(buffer, 10, file) {
    printf("%s", buffer);
}
复制代码
  • fputs: 写入文件


函数原型:
int fputs(const char *str, FILE *stream)
使用函数:
FILE *file = fopen("文件名", "");
if (!file) {
    // 文件打开失败
}
fputs("xurui", file);
fclose(file);
复制代码
  • EOF: 文件的末尾(End of File)


内存区域


  • 从低地址到高地址,依次为:代码区、数据区、堆区和栈区。
  • 代码区: 可执行文件的二进制代码
  • 数据区:
  • data 段: 已经初始化的静态变量和全局变量
  • bss 段: 未初始化的静态变量和全局变量
  • rodata 段: 常量值(如字符串常量)
  • 栈区: 存储函数调用对应的栈帧
  • 堆区: 动态分配的内存

image.png


—— 图片引用自网络

目录
相关文章
|
6月前
|
存储 人工智能 算法
【C语言】自学终极笔记
【C语言】自学终极笔记
106 0
|
3月前
|
测试技术 C语言 C++
【C语言刷题训练——6】鹏哥C语言刷题训练营笔记,含代码讲解改进
【C语言刷题训练——6】鹏哥C语言刷题训练营笔记,含代码讲解改进
|
3月前
|
存储 C语言
【C语言】鹏哥C语言刷题训练营——第5节内容笔记(含代码全面分析和改进,讲解)
【C语言】鹏哥C语言刷题训练营——第5节内容笔记(含代码全面分析和改进,讲解)
|
5月前
|
C语言
|
5月前
|
C语言
|
6月前
|
C语言
C语言(指针详解)重点笔记:指针易错点,都是精华
C语言(指针详解)重点笔记:指针易错点,都是精华
75 0
|
存储 编译器 C语言
C语言笔记第03章:数组(四)
C语言笔记第03章:数组
95 0
|
编译器 C语言 索引
C语言笔记第03章:数组(一)
C语言笔记第03章:数组
104 0
|
6月前
|
NoSQL Redis C语言
|
C语言
C语言初阶 牛客网刷题笔记(将持续更新..)
C语言初阶 牛客网刷题笔记(将持续更新..)
C语言初阶 牛客网刷题笔记(将持续更新..)