指针

  1. // 定义指针变量
  2. 类型 *变量名;
  3. int *p1 = NULL; // 空指针
  4. // 连续定义指针变量
  5. int *p2, *p3; // 指针变量未初始化,保存的地址值为随机值;野指针,
  6. int *p4, p5; // p5 只是普通int变量
  7. // 使用 &取地址符 获取变量地址
  8. int a = 120;
  9. int *pa = &a;
  10. // 使用 * 解引用运算符 来获取指定指向空间的值
  11. printf("%d\n", *pa) //
  • 指针变量占内存空间大小有操作系统决定:32位系统地址值32位,4字节;64位系统地址值64位,8字节
  • 类型是给指针变量指向的内存区域保存的值使用
  • 定义指针变量时,未赋值的都赋值成 NULL ,防止野指针
  • 指针使用时应先判断是否是空指针 p == NULL , 如果是NULL则不应使用该指针

指针运算

  • 指针可和整数做加减法运算,计算结果和指针指向的变量数据类型有关 ```c 如 char c[] = {‘A’, ‘B’, ‘C’, ‘D’}; char *pc = c; // 假设 c[0] 的地址为 0x100000 pc++; // pc 指向 c[1] 地址则为 0x100001 // … 以此类推

如果 指针指向其他类型则: int arr[] = {1,2,3,4,5}; int *pi = arr; // 假设 arr[0] 的地址为 0x100000 pi++; // pi 指向 arr[1] 地址则为 0x100004 地址增加了4

// 数组和指针关系 arr[i] = pi[i] = (arr+i) = (pi+i)

  1. <a name="GJkCM"></a>
  2. ## 指针与常量
  3. 常量定义初始化后,不能进行重新复制,否则编译报错
  4. ```c
  5. const 变量类型 变量名 = 值;
  6. const int num = 33;
  7. num = 55; // 编译报错

常量指针

  • 可修改指针变量的值
  • 但不能修改指针变量所指向内存空间的值 ```c const 类型* 变量名 = 值;

int a = 44; int b = 66; const int pa = &a; pa = 55; // 编译报错 pa = &b; // 正常

  1. <a name="2bIWp"></a>
  2. ### 指针常量
  3. - 不可修改指针变量的值
  4. - 可修改指针变量所指向内存空间的值
  5. ```c
  6. 类型* const 变量名 = 值;
  7. int a = 44;
  8. int b = 66;
  9. const int* pa = &a;
  10. *pa = 55; // 正常
  11. pa = &b; // 编译报错

常量指针常量

  • 即不能修改指针变量的值,也不能修改指针所指向内存空间的值 ```c const 类型* const 变量名 = 值;

int a = 44; int b = 66; const int pa = &a; pa = 55; // 编译报错 pa = &b; // 编译报错

  1. <a name="U8KJS"></a>
  2. ## 无数据类型指针 `void *`
  3. ```c
  4. int a = 22;
  5. void *p = &a;
  6. //不能直接用解引用操作符
  7. printf("%d\n", *p); // 错误
  8. // 指针运算, 假如 p => 0x1000
  9. // p++; // 则p => 0x1001
  10. // p++; // 则p => 0x1002
  11. // 转换成其他类型指针
  12. char* p1 = (char*)p;
  13. short* p2 = (short*)p;
  14. int* p3 = (int*)p\;
  • 通过此指针变量是无法获知指向的内存区域保存的数据的数据类型
  • 不能直接对无数据类型指针进行解引用存在 * ,必须做数据类型转换
  • 无类型指针加减法,指针变量加减几,则实际地址就加减几

指针与函数

指针形参

函数通过指针能够访问操作指向的内存

  1. // 交换两个数
  2. void swap(int *num1, int *num2)
  3. {
  4. int tmp = *num1;
  5. *num1 = *num2;
  6. *num2 = tmp;
  7. }
  8. int main(void)
  9. {
  10. int a=10, b=20;
  11. swap(&a, &b); // a = 20, b=10
  12. }

指针函数

指针作为函数返回值
注意防止返回野指针

  1. 返回值数据类型 *函数名(形参表){函数结构体}
  2. int g_a = 111;
  3. static int g_b = 222;
  4. int *func(void)
  5. {
  6. int a = 333;
  7. static b = 444;
  8. // 以下返回野指针,a的生命周期为当前函数,return后函数结束,将释放a的内存空间
  9. // &a指针返回后,&a的地址指向地址空间已释放
  10. return &a;
  11. // 以下返回都正常
  12. // return &b;
  13. // return &g_a;
  14. // return &g_b;
  15. }

字符串

  1. printf("%s\n", "Hello");
  2. char s[] = "Hello World!";
  3. printf("%s\n", s);
  4. printf("%s\n", "Hello World!"
  5. "Hello C!"); // gcc自动合并 -> "Hello World!Hello C!"
  6. char *p = "Hello C String!";
  7. printf("%s\n", p);
  • 由一串连续的字符组成,并用 "" 包含起来,并且最后一个字符必须是 \0
  • \0 表示字符串结束,为ASCII码是0
  • 占位符:”%s“
  • 字符串占用的内存空间是连续的,并且每个字节存储一个字符
  • 多个并列的字符串将会有gcc合并成一个字符串

指针

  1. char *p = "Hello C String!";
  2. printf("%s\n", p);
  3. // 不同修改字符串的值
  4. *(p+2) = 'L'; // 报错
  • 定义一个字符串指针变量并且指向一个字符串,泵站是指向这个字符串的首地址
  • 如果让一个字符串指针变量指向一个字符串,此字符串无需根 \0 ,gcc自动添加
  • 不能通过指针修改字符串的值

数组

  1. char s1[] = "abc";
  2. char s2[] = {'a', 'b', 'c', '\0'}; // 如果s2是字符串,则必须添加 \0
  3. // 对于字符数组中的元素都是可以修改的
  4. s1[0] = 'A';
  5. *(s2+1) = 'B';

字符串函数

使用字符串函数需先导入 #include <string.h>

strlen 字符串长度

  1. // 计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。
  2. size_t strlen(const char *str)
  3. strlen("abc"); // 3
  4. char *p = "abc";
  5. strlen(p); // 3

strcat 字符串拼接

  • 需注意拼接用的char数据长度需要能容纳拼接字符串后长度, 否则会出现内存溢出问题 ```c // 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。 char strcat(char dest, const char *src)

char s[50] = “hello”; // 这个长度必须是大于 两个字符串拼接后的长度 char *p = NULL;

p = strcat(s, “ world!!”); // p -> hello world!! // s -> hello world!!

  1. <a name="D3tCS"></a>
  2. ### strcmp 字符串比较
  3. ```c
  4. //把 str1 所指向的字符串和 str2 所指向的字符串进行比较。
  5. int strcmp(const char *str1, const char *str2)
  6. 如果返回值小于 0,则表示 str1 小于 str2。
  7. 如果返回值大于 0,则表示 str1 大于 str2。
  8. 如果返回值等于 0,则表示 str1 等于 str2。

strcpy 字符串拷贝

  • 和strcat相同需要注意目标数组的长度要大于或等于拷贝数组 ```c //把 src 所指向的字符串复制到 dest。该函数返回一个指向最终的目标字符串 dest 的指针。 char strcpy(char dest, const char *src)
  1. <a name="RV7UI"></a>
  2. ### sprintf 字符串格式化
  3. - 属于 `stdio.h` 头文件
  4. ```c
  5. // 将 字符串格式化输出到str中,
  6. // 如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。
  7. // 如果失败,则返回一个负数。
  8. int sprintf(char *str, const char *format, ...)
  9. int n;
  10. char s[50] = {0};
  11. n = sprintf(s, "age: %d, name:%s", 15, "张三");

预处理指令

#include 包含头文件

  • #include <头文件名.h> gcc编译器将直接到 /usr/include 目录下找指定的头文件
  • #include "头文件名.h" gcc先到当前目录下查找头文件,如果没有再到 /usr/include 目录下找
  • gcc 编译时,可通过 -I 头文件目录 参数来指定头文件所在路径

#define 宏定义

  • 宏名建议使用大写,续行用 \
  • 提高代码的可移植性,让代码改起来方便

常量宏

  1. #define 宏名 (值)
  2. // 例如
  3. #define PI (3.14)

参数宏(宏函数)

  1. #define 宏名(宏参数) (宏值)
  2. // 例子
  3. #define SQUARE(x) ((x) * (x)) // 宏参数的小括号不能省略
  4. int main(void)
  5. {
  6. printf("%d\n", SQUARE(10)); // 10 * 10
  7. printf("%d\n", SQUARE(3+7)); // (3+7) * (3+7) ;如果少了小括号 3+7 * 3+7
  8. return 0;
  9. }
  • gcc 编译器先将宏参数替换成实际值,然后将代码中宏名最终全部替换成宏值
  • 宏值里的宏参数小括号不能少


#(转换字符串)和 ##(粘贴)

  1. #define PRINT(N) printf(#N"=%d\n", N)
  2. int a = 10, b = 20;
  3. PRINT(a + b); // => printf("a + b""=%d\n", a + b)
  4. #define ID(N) id##N
  5. int ID(1) = 10, ID(2) = 20; // => int id1 = 10, id2 = 20;

编译器定义宏

  • __FILE__ :当前文件名
  • __LINE__ :当前行号
  • __FUNCTION__ :当前所在函数名
  • __DATE__ :文件创建的日期
  • __TIME__ :文件创建的时间

用户预定义宏

  • 通过 gcc 的 -D 选项来预定义宏 ```c gcc -DSIZE=250 -DPATH=\”~/\”

printf(“%d %s”, SIZE, PATH);

  1. <a name="WgEOU"></a>
  2. ### 条件编译
  3. - `#if` 如果
  4. - `#ifdef` 如果定义了……
  5. - `#ifndef` 如果没有定义……
  6. - `#elif` 否则如果
  7. - `#else` 否则
  8. - `#endif` 结束配合 `#if` `#ifdef` `#ifndef` 配合使用
  9. - `#undef` 取消定义,功能和 `#define` 相反
  10. <a name="t"></a>
  11. ### 头文件
  12. - 头文件:负责变量的声明,自定义数据类型的声明和函数声明
  13. - 一个源文件对应一个头文件,并且源文件包含自己对应的头文件
  14. - 如果头文件中有相同的内容,建议放到一个公共的头文件中,其他头文件只需包含该头文件即可
  15. ```c
  16. 头文件:tools.h
  17. -----------------
  18. #ifndef __TOOLS_H
  19. #define __TOOLS_H
  20. // 头文件内容
  21. #endif
  • 为防止头文件内容重复定义
  • 一般定义的宏名为 __头文件名_H

结构体 struct

  1. // 声明结构体, 一般结构体声明放在头文件中
  2. struct 结构体名 {
  3. 结构体成员...
  4. };
  5. // 定义结构体,定义结构体放在c文件中
  6. struct 结构体名 结构体变量名;
  7. // 匿名结构体, 只能使用一次
  8. struct {
  9. 结构体成员...
  10. }结构体变量名;
  11. // 例如
  12. struct student {
  13. char *name[50];
  14. int age;
  15. int id;
  16. float score;
  17. };
  18. struct student s1;

typedef 定义别名

  1. // 给基本类型定义别名
  2. typedef 原数据类型 别名;
  3. // 例如
  4. typedef char s8;
  5. typedef unsigned char u8;
  6. typedef short s16;
  7. typedef unsigned char u16;
  8. typedef int s32;
  9. typedef unsigned int u32;
  10. typedef long long s64;
  11. typedef unsigned long long u64;
  12. typedef float f32;
  13. typedef double f64;
  14. // 给结构体定义别名, 一般结构体别名后加 _t
  15. typedef struct student {
  16. char *name[50];
  17. int age;
  18. int id;
  19. float score;
  20. }std_t;
  21. // 使用别名定义变量
  22. std_t student1;

初始化和成员访问

  1. typedef struct {
  2. char *name[50];
  3. int age;
  4. int id;
  5. float score;
  6. }std_t;
  7. // 方式1
  8. std_t student1 = {"张三", 16, 10001, 99.5}
  9. // 方式2, 可不用全部初始化
  10. std_t student2 = {
  11. .id = 10003,
  12. .name = "李四",
  13. .age = 15,
  14. }
  15. // 访问结构体变量成员,使用 .
  16. printf("name: %s, age: %d", student1.name, student1.age);
  17. // 访问结构体指针成员,使用 ->
  18. stu_t *p = &student2;
  19. printf("name: %s, age: %d", p->name, p->age);

结构体函数参数

  • 直接传结构体变量,为值拷贝,不能通过形参改变结构体成员值
  • 传结构体指针变量,只拷贝指针地址4字节,可通过形参改变成员值
  • 一般都传结构体指针,如果不让函数修改结构体值时形参加const ```c void func(std_t *pst) { // 可通过pst修改结构体成员值 pst->age = 17; }

void func2(const std_t *pst) { // 不能通过pst修改结构体成员值
}

  1. <a name="X3jps"></a>
  2. ## 结构体内存对齐
  3. - gcc 对结构体成员编译时,默认按4字节对齐
  4. ```c
  5. struct A {
  6. char buf[2], // 4字节
  7. int val, // 4字节
  8. };
  9. #pragma pack(1) // 强制让gcc按1个字节对齐
  10. struct B {
  11. char buf[2], // 2字节
  12. int val, // 4字节
  13. };
  14. #pragma pack() // 让gcc恢复到按4个字节对齐

联合体 union

  • 声明方法与结构体相同,只是将 struct 关键字 该为 union 关键字
  • 联合体中所有成员是共用一块内存
  • 联合体占用的内存按成员中占内存最大的来算

判断CPU的大端还是小端

  • 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中
  • 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中
  • image.png ```c typedef union data { int a; char c; }data_t;

data_t val = 1;

if (val.c == 1) printf(“小端\n”); else printf(“大端”);

  1. <a name="mei"></a>
  2. # 枚举 enum
  3. - 枚举的本质就是一堆整数的集合,列表,就是给整数取了个别名
  4. ```c
  5. enum 枚举数据类型 {枚举值};
  6. enum COLOR {RED, GREEN, BLUE}; // RED = 0, GREEN = 1, BLUE = 2
  7. enum STATUS {SUCCESS, SERVER_ERROR=10, UNKNOWN_ERROR};
  8. // SUCCESS = 0, SERVER_ERROR = 10, UNKNOWN_ERROR = 11

函数指针

  • 函数名就是整个函数里面代码的首地址
  1. // 声明函数指针类型
  2. 函数返回值类型 (*函数名)(形参表);
  3. //例如
  4. int (*pfunc)(int a, int b);
  5. // 使用typedef声明,常用
  6. typedef int (*pfunc_t)(int a, int b);
  7. // 定义函数指针变量
  8. pfunc_t pf = NULL;
  9. //------------------------
  10. // 初始化
  11. int add(int a, int b){
  12. return a + b;
  13. }
  14. // 回调函数
  15. int compute(int a, int b, pfunc_t func) {
  16. return func(a, b);
  17. }
  18. int main(void){
  19. pfunc_t pf = add;
  20. int res = pf(10, 20);
  21. res = compute(10, 20, add);
  22. }

多级指针(二级指针)

  • 二级指针:指向一级指针的指针,存放一级指针变量的地址
  • 二级指针和字符串

    1. // 交换两个字符串
    2. void swap(char **p1, char **p2)
    3. {
    4. char *tmp = *p1;
    5. *p1 = *p2;
    6. *p2 = tmp;
    7. }
    8. int main(void)
    9. {
    10. char *p1 = "Hello";
    11. char *p2 = "world";
    12. swap(&p1, &p2);
    13. return 0;
    14. }
  • 二级指针和字符指针数组 ```c char p[] = {“C”, “Python”, “Go”}; char *pstr = p

*(pstr + 0 ); // p[0] => “C”

  1. <a name="VmMpu"></a>
  2. ## main函数标准
  3. ```c
  4. int main(int argc, char **args){
  5. return 0;
  6. }
  7. // or
  8. int main(int argc, char *args[]) {
  9. }
  • argc, args 为运行程序时,可以在命令行终端上给程序传递参数
  • argc,用户传入参数个数
  • args,用户传入实际参数
  • 第一个参数为执行函数命令
    1. ./hello 100 hello
    2. argc = 3
    3. args[0] = "./hello"
    4. args[1] = "100"
    5. args[2] = "hello"

字符串转整数 strtoul

  • 包含头文件 strlib.h ```c unsigned long int strtoul(const char str, char *endptr, int base); //———————————————

    include

    include

int main(int argc, char *args) { char str[30] = “2030300 This is test”; char ptr; long ret;

ret = strtoul(str, &ptr, 10); printf(“数字(无符号长整数)是 %lu\n”, ret); printf(“字符串部分是 |%s|”, ptr);

return(0); }

  1. - **str:**要转换为无符号长整数的字符串。
  2. - **endptr**:对类型为 char* 的对象的引用,其值由函数设置为 str 中数值后的下一个字符。不需要可传NULL
  3. - **base:**基数(进制),必须介于 2 36(包含)之间,或者是特殊值 0
  4. <a name="h6bBc"></a>
  5. # malloc 和 free 内存分配和释放
  6. - 包含头文件 `stdlib.h`
  7. ```c
  8. // 分配内存, 返回内存的首地址
  9. int *p = (int *)malloc(8);
  10. if (NULL == p) {
  11. printf("内存分配失败\n");
  12. return -1;
  13. }
  14. *(p + 0) = 2;
  15. *(p + 1) = 5;
  16. // 释放内存
  17. free(p);
  18. p = NULL;