指针
// 定义指针变量类型 *变量名;int *p1 = NULL; // 空指针// 连续定义指针变量int *p2, *p3; // 指针变量未初始化,保存的地址值为随机值;野指针,int *p4, p5; // p5 只是普通int变量// 使用 &取地址符 获取变量地址int a = 120;int *pa = &a;// 使用 * 解引用运算符 来获取指定指向空间的值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)
<a name="GJkCM"></a>## 指针与常量常量定义初始化后,不能进行重新复制,否则编译报错```cconst 变量类型 变量名 = 值;const int num = 33;num = 55; // 编译报错
常量指针
- 可修改指针变量的值
- 但不能修改指针变量所指向内存空间的值 ```c const 类型* 变量名 = 值;
int a = 44; int b = 66; const int pa = &a; pa = 55; // 编译报错 pa = &b; // 正常
<a name="2bIWp"></a>### 指针常量- 不可修改指针变量的值- 可修改指针变量所指向内存空间的值```c类型* const 变量名 = 值;int a = 44;int b = 66;const int* pa = &a;*pa = 55; // 正常pa = &b; // 编译报错
常量指针常量
- 即不能修改指针变量的值,也不能修改指针所指向内存空间的值 ```c const 类型* const 变量名 = 值;
int a = 44; int b = 66; const int pa = &a; pa = 55; // 编译报错 pa = &b; // 编译报错
<a name="U8KJS"></a>## 无数据类型指针 `void *````cint a = 22;void *p = &a;//不能直接用解引用操作符printf("%d\n", *p); // 错误// 指针运算, 假如 p => 0x1000// p++; // 则p => 0x1001// p++; // 则p => 0x1002// 转换成其他类型指针char* p1 = (char*)p;short* p2 = (short*)p;int* p3 = (int*)p\;
- 通过此指针变量是无法获知指向的内存区域保存的数据的数据类型
- 不能直接对无数据类型指针进行解引用存在
*,必须做数据类型转换 - 无类型指针加减法,指针变量加减几,则实际地址就加减几
指针与函数
指针形参
函数通过指针能够访问操作指向的内存
// 交换两个数void swap(int *num1, int *num2){int tmp = *num1;*num1 = *num2;*num2 = tmp;}int main(void){int a=10, b=20;swap(&a, &b); // a = 20, b=10}
指针函数
指针作为函数返回值
注意防止返回野指针
返回值数据类型 *函数名(形参表){函数结构体}int g_a = 111;static int g_b = 222;int *func(void){int a = 333;static b = 444;// 以下返回野指针,a的生命周期为当前函数,return后函数结束,将释放a的内存空间// &a指针返回后,&a的地址指向地址空间已释放return &a;// 以下返回都正常// return &b;// return &g_a;// return &g_b;}
字符串
printf("%s\n", "Hello");char s[] = "Hello World!";printf("%s\n", s);printf("%s\n", "Hello World!""Hello C!"); // gcc自动合并 -> "Hello World!Hello C!"char *p = "Hello C String!";printf("%s\n", p);
- 由一串连续的字符组成,并用
""包含起来,并且最后一个字符必须是\0 \0表示字符串结束,为ASCII码是0- 占位符:”%s“
- 字符串占用的内存空间是连续的,并且每个字节存储一个字符
- 多个并列的字符串将会有gcc合并成一个字符串
指针
char *p = "Hello C String!";printf("%s\n", p);// 不同修改字符串的值*(p+2) = 'L'; // 报错
- 定义一个字符串指针变量并且指向一个字符串,泵站是指向这个字符串的首地址
- 如果让一个字符串指针变量指向一个字符串,此字符串无需根
\0,gcc自动添加 - 不能通过指针修改字符串的值
数组
char s1[] = "abc";char s2[] = {'a', 'b', 'c', '\0'}; // 如果s2是字符串,则必须添加 \0// 对于字符数组中的元素都是可以修改的s1[0] = 'A';*(s2+1) = 'B';
字符串函数
使用字符串函数需先导入 #include <string.h>
strlen 字符串长度
// 计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。size_t strlen(const char *str)strlen("abc"); // 3char *p = "abc";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!!
<a name="D3tCS"></a>### strcmp 字符串比较```c//把 str1 所指向的字符串和 str2 所指向的字符串进行比较。int strcmp(const char *str1, const char *str2)如果返回值小于 0,则表示 str1 小于 str2。如果返回值大于 0,则表示 str1 大于 str2。如果返回值等于 0,则表示 str1 等于 str2。
strcpy 字符串拷贝
- 和strcat相同需要注意目标数组的长度要大于或等于拷贝数组 ```c //把 src 所指向的字符串复制到 dest。该函数返回一个指向最终的目标字符串 dest 的指针。 char strcpy(char dest, const char *src)
<a name="RV7UI"></a>### sprintf 字符串格式化- 属于 `stdio.h` 头文件```c// 将 字符串格式化输出到str中,// 如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。// 如果失败,则返回一个负数。int sprintf(char *str, const char *format, ...)int n;char s[50] = {0};n = sprintf(s, "age: %d, name:%s", 15, "张三");
预处理指令
#include 包含头文件
#include <头文件名.h>gcc编译器将直接到/usr/include目录下找指定的头文件#include "头文件名.h"gcc先到当前目录下查找头文件,如果没有再到/usr/include目录下找- gcc 编译时,可通过
-I 头文件目录参数来指定头文件所在路径
#define 宏定义
- 宏名建议使用大写,续行用
\ - 提高代码的可移植性,让代码改起来方便
常量宏
#define 宏名 (值)// 例如#define PI (3.14)
参数宏(宏函数)
#define 宏名(宏参数) (宏值)// 例子#define SQUARE(x) ((x) * (x)) // 宏参数的小括号不能省略int main(void){printf("%d\n", SQUARE(10)); // 10 * 10printf("%d\n", SQUARE(3+7)); // (3+7) * (3+7) ;如果少了小括号 3+7 * 3+7return 0;}
- gcc 编译器先将宏参数替换成实际值,然后将代码中宏名最终全部替换成宏值
- 宏值里的宏参数小括号不能少
#(转换字符串)和 ##(粘贴)
#define PRINT(N) printf(#N"=%d\n", N)int a = 10, b = 20;PRINT(a + b); // => printf("a + b""=%d\n", a + b)#define ID(N) id##Nint 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);
<a name="WgEOU"></a>### 条件编译- `#if` 如果- `#ifdef` 如果定义了……- `#ifndef` 如果没有定义……- `#elif` 否则如果- `#else` 否则- `#endif` 结束配合 `#if` `#ifdef` `#ifndef` 配合使用- `#undef` 取消定义,功能和 `#define` 相反<a name="t"></a>### 头文件- 头文件:负责变量的声明,自定义数据类型的声明和函数声明- 一个源文件对应一个头文件,并且源文件包含自己对应的头文件- 如果头文件中有相同的内容,建议放到一个公共的头文件中,其他头文件只需包含该头文件即可```c头文件:tools.h-----------------#ifndef __TOOLS_H#define __TOOLS_H// 头文件内容#endif
- 为防止头文件内容重复定义
- 一般定义的宏名为
__头文件名_H
结构体 struct
// 声明结构体, 一般结构体声明放在头文件中struct 结构体名 {结构体成员...};// 定义结构体,定义结构体放在c文件中struct 结构体名 结构体变量名;// 匿名结构体, 只能使用一次struct {结构体成员...}结构体变量名;// 例如struct student {char *name[50];int age;int id;float score;};struct student s1;
typedef 定义别名
// 给基本类型定义别名typedef 原数据类型 别名;// 例如typedef char s8;typedef unsigned char u8;typedef short s16;typedef unsigned char u16;typedef int s32;typedef unsigned int u32;typedef long long s64;typedef unsigned long long u64;typedef float f32;typedef double f64;// 给结构体定义别名, 一般结构体别名后加 _ttypedef struct student {char *name[50];int age;int id;float score;}std_t;// 使用别名定义变量std_t student1;
初始化和成员访问
typedef struct {char *name[50];int age;int id;float score;}std_t;// 方式1std_t student1 = {"张三", 16, 10001, 99.5}// 方式2, 可不用全部初始化std_t student2 = {.id = 10003,.name = "李四",.age = 15,}// 访问结构体变量成员,使用 .printf("name: %s, age: %d", student1.name, student1.age);// 访问结构体指针成员,使用 ->stu_t *p = &student2;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修改结构体成员值
}
<a name="X3jps"></a>## 结构体内存对齐- gcc 对结构体成员编译时,默认按4字节对齐```cstruct A {char buf[2], // 4字节int val, // 4字节};#pragma pack(1) // 强制让gcc按1个字节对齐struct B {char buf[2], // 2字节int val, // 4字节};#pragma pack() // 让gcc恢复到按4个字节对齐
联合体 union
- 声明方法与结构体相同,只是将
struct关键字 该为union关键字 - 联合体中所有成员是共用一块内存
- 联合体占用的内存按成员中占内存最大的来算
判断CPU的大端还是小端
- 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中
- 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中
```c
typedef union data {
int a;
char c;
}data_t;
data_t val = 1;
if (val.c == 1) printf(“小端\n”); else printf(“大端”);
<a name="mei"></a># 枚举 enum- 枚举的本质就是一堆整数的集合,列表,就是给整数取了个别名```cenum 枚举数据类型 {枚举值};enum COLOR {RED, GREEN, BLUE}; // RED = 0, GREEN = 1, BLUE = 2enum STATUS {SUCCESS, SERVER_ERROR=10, UNKNOWN_ERROR};// SUCCESS = 0, SERVER_ERROR = 10, UNKNOWN_ERROR = 11
函数指针
- 函数名就是整个函数里面代码的首地址
// 声明函数指针类型函数返回值类型 (*函数名)(形参表);//例如int (*pfunc)(int a, int b);// 使用typedef声明,常用typedef int (*pfunc_t)(int a, int b);// 定义函数指针变量pfunc_t pf = NULL;//------------------------// 初始化int add(int a, int b){return a + b;}// 回调函数int compute(int a, int b, pfunc_t func) {return func(a, b);}int main(void){pfunc_t pf = add;int res = pf(10, 20);res = compute(10, 20, add);}
多级指针(二级指针)
- 二级指针:指向一级指针的指针,存放一级指针变量的地址
二级指针和字符串
// 交换两个字符串void swap(char **p1, char **p2){char *tmp = *p1;*p1 = *p2;*p2 = tmp;}int main(void){char *p1 = "Hello";char *p2 = "world";swap(&p1, &p2);return 0;}
二级指针和字符指针数组 ```c char p[] = {“C”, “Python”, “Go”}; char *pstr = p
*(pstr + 0 ); // p[0] => “C”
<a name="VmMpu"></a>## main函数标准```cint main(int argc, char **args){return 0;}// orint main(int argc, char *args[]) {}
- argc, args 为运行程序时,可以在命令行终端上给程序传递参数
- argc,用户传入参数个数
- args,用户传入实际参数
- 第一个参数为执行函数命令
./hello 100 helloargc = 3args[0] = "./hello"args[1] = "100"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); }
- **str:**要转换为无符号长整数的字符串。- **endptr**:对类型为 char* 的对象的引用,其值由函数设置为 str 中数值后的下一个字符。不需要可传NULL- **base:**基数(进制),必须介于 2 和 36(包含)之间,或者是特殊值 0。<a name="h6bBc"></a># malloc 和 free 内存分配和释放- 包含头文件 `stdlib.h````c// 分配内存, 返回内存的首地址int *p = (int *)malloc(8);if (NULL == p) {printf("内存分配失败\n");return -1;}*(p + 0) = 2;*(p + 1) = 5;// 释放内存free(p);p = NULL;
