- 未定义行为
数组越界
int arr[3] = {1, 2, 3};
printf("%d\n", arr[5]); // 越界访问,结果未定义
解引用空指针
int *ptr = NULL;
printf("%d\n", *ptr); // 解引用空指针,结果未定义
未初始化的局部变量
int x;
printf("%d\n", x); // x 未初始化,结果未定义
浮点数除以零
float x = 1.0;
float y = x / 0.0; // 浮点数除以零,结果未定义
整数除以零
int x = 10;
int y = x / 0; // 整数除以零,结果未定义
符号溢出
signed char x = 127;
x = x + 1; // signed char 溢出,结果未定义
误的类型转换
int *ptr = (int *)malloc(sizeof(int));
float *fptr = (float *)ptr; // 错误的类型转换,结果未定义
内存越界
int *ptr2 =(int *) malloc(sizeof(int) );
free(ptr2);
*ptr2 = 10;
- 内存管理
malloc() 函数:用于动态分配内存。它接受一个参数,即需要分配的内存大小(以字节为单位),并返回一个指向分配内存的指针。 calloc() 函数:用于动态分配内存,「并将其初始化为零」。它接受两个参数,即需要分配的内存块数和每个内存块的大小(以字节为单位),并返回一个指向分配内存的指针。
- 指针变量类型
int * p = NULL;
指针变量的作用:
1、决定了指针变量所取空间内容的宽度
2、决定了指针变量 + 1 跳过的单位跨度
void testPointType()
{
int num = 10;
int *p = NULL;
p = #
// 指针变量的跨度, p是 int 型, p+1 跳过的是 int 占用的 4个字节空间
printf("&num=%u\n", &num);
//p1 +1 比 p1 的值大 4
printf("p=%u\n", p);
printf("p+1=%u\n", p + 1);
char *p1 = #
//p1 +1 比 p1 的值大 1
printf("p1=%u\n", p1);
printf("p1+1=%u\n", p1 + 1);
// 如何取出 0x0102
short *p4 = #
// p4指向的变量类型是 short,占两个字节,因此 +=1 跳两个字节,由 04 的位置调到 01
p4 += 1;
printf("*p4=%#x\n", *p4);
}
- 变量类型取值
void testPointType2()
{
void testPointType2()
{
// 内存中村方式为 04 03 02 01
int num = 0x01020304;
int *p1 = #
// 输出 0x01020304
printf("*p1=%#x\n", *p1);
short *p2 = #
// 由于 short 类型是 2 个字节,因此 *p2智能取出前两个字节,输出 0x0304
printf("*p2=%#x\n", *p2);
char *p3 = #
// 由于 char 类型是 1 个字节,因此 *p3智能取出前 1 个字节,输出 0x04
printf("*p3=%#x\n", *p3);
// 如何取出 0x0102
short *p4 = #
// p4指向的变量类型是 short,占两个字节,因此 +=1 跳两个字节,由 04 的位置调到 01
p4 += 1;
printf("*p4=%#x\n", *p4);
// 如何取出 0x0203
char *p5 = #
// 指向 03
p5 += 1;
// 利用 short * 取 2 个字节
printf("*p5=%#x\n", *(short *)p5);
}
}
- 在 c 中被调用的函数,必须定义在调用函数的前面,或者是在被调用的函数前面做申明
- 字符串拼接时,需要先确保添加的字符串容量是否足够,如下代码 str1 空间是 6 个字符(未显示声明时,其容量就是字符长度,包括末尾的 "\0"),在把 str2 拼接到 str1 的末尾时,会报缓存区溢出的错误,原因是 str1的空间不够,将 str1 空间改为 20 就可以了
//修改前
char str1[] = "hello";
char str2[] = "world";
strcat(str1, str2);
//修改后
char str1[20] = "hello";
char str2[] = "world";
strcat(str1, str2);
-
分配了对象后要主动释放,否则会有内存泄漏
-
没有直接的库函数将字符串改为大写或小写?
-
需在给定字符数组的大小时在原有的字符串的字符数上加 1。
-
定义字符串时要指定大小,例如 char title[50]
-
vs 中如何查看 string.h 中库函数的源码
-
使用 printf的 %x 格式化输出时,打印的数据类型必须和占位符类型一致,否则运行会报错,是否有类似 java 中的 %s 这种能兼容各种数据类型的格式化占位符?
-
定义变量时,如果赋值的类型和定义的类型不一致时,编译期不会提示,而是等到运行期才会提示 例如以下 5.1 是浮点型,使用 int 去接收,AS 编译期会报错, vs 不会报错
int divisor = 5.1 ;
- 可变参数
int sum(int count, ...)
{
int total = 0;
va_list args;
// 初始化 args 为可变参数列表
va_start(args, count);
for (int i = 0; i < count; i++)
{
//va_arg(args, int): 从 va_list 对象中按给定类型获取下一个参数。
total += va_arg(args, int);
}
// 清理工作 清理 va_list 对象,完成可变参数的处理。
va_end(args);
return total;
}
- 使用 printf 输出字符串时,指针不需要解引用
当一个指针作为 printf 函数的参数时,你不需要使用解引用操作符 * 来获取指针指向的值。这是因为 printf 函数的格式字符串会告诉 printf 函数期待的参数类型,而 %s 格式说明符明确指出它期待的是一个指向字符数组(即字符串)的指针。
char *str = "Hello";
int d =10;
//ok
printf("str: %s\n", str); // 使用指针直接传递
//error
printf("str: %s\n", *str); // 使用解引用操作符 *,这是不必要的
-
使用系统自带的库函数,不会自动导入 .h 文件,需要先手动导入才能使用
-
获取代码执行耗时时间
#include <time.h>
struct timeval start, end;
float during;
// 计算开始的时候,获取当前时间点
gettimeofday(&start, NULL);
//代码执行
func();
// 计算结束的时候,获取当前时间点
gettimeofday(&end, NULL);
during = (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec);
- pthread_signal 和 pthread_boraodcast 区别
pthread_signal 一次只能激活一个条件变量, pthread_boraodcast 可以一次激活多个条件变量
- 变量初始化 char str[], 定义变量如果没有初始化时默认是没有值的(也不是空字符),使用 memset,第 2 个参数必须是 char 类型,如果第 2 个参数是 int 之类的,其实是无效的
char str[20];
//将字符串 str 中的字符全部初始化为 0 (空字符)
memset(str,0,sizeof(str));
//将字符串 str 中的字符全部初始化为 1
memset(str,1,sizeof(str));
//将字符串 str 中的字符全部初始化为 'a'
memset(str,’a‘,sizeof(str));
-
定义字符串变量时要定义大小,例如 char name[20],方括号内要填写大小
-
在 C 语言中,不能直接使用
=来给字符数组(如name)赋字符串值。应该使用strcpy函数来复制字符串。 -
结构体传承的时候,建议传地址,而不是传值,传地址性能会好一些(只会有4个字节),同时为了防止修改指针内容,建议加上 const 关键字,传值的话,相当于把所有参数全部拷贝了一遍
-
C 语言的源代码 -> 预编译 -> 编译 -> 链接 > 可执行程序
预编译: 删除注释, 替换 define 常量,
- 删除数组中的某个元素
void removeElement(int arr[], int *size, int index) {
if (index < 0 || index >= *size) {
printf("Index out of bounds.\n");
return;
}
for (int i = index; i < *size - 1; i++) {
arr[i] = arr[i + 1];
}
(*size)--; // 减少数组的有效大小
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
int indexToRemove = 2; // 假设我们要删除索引为 2 的元素,即数字 3
removeElement(arr, &size, indexToRemove);
printf("Array after removal: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
-
static 修饰的变量和函数只能在本文件内使用
-
堆区、栈区、静态区
栈区: 局部变量、形参
堆区: 动态内存分配,例如使用 malloc 函数
静态区(数据段):全局变量、静态变量
代码段:存放函数体(类成员函数和全局函数)的二进制代码
- 数组初始化
char
- malloc 和 calloc 的区别
1、参数不同
2、calloc 在返回地址之前把申请的空间的每个字节初始化为 0,malloc 不会初始化,因此 malloc 的效率会高一些
- 运行出现错误时,如何定义错误
- 常见的内存分配错误
1、对空指针解引用
2、对动态开辟的内存空间越界访问
3、对非动态开辟内存执行 free
5、对同一块内存多次释放
/**
* 对同一块内存多次释放
*/
void freeSameMemoryMultiTime(){
int * ptr =(int *) malloc(19);
if (ptr == NULL)
{
printf("failed to allcoate memory\n");
}
free(ptr);
free(ptr);
ptr = NULL;
}
-
free 释放之后,并不会把指针置为 NULL,需要手动置为 NULL 才行
-
文件操作模式
- 预处理
# 开头的都是预处理指令,例如:
#include. #define
#define req register
# define reg int a -> register int a
- 编译和链接
编译:把源代码编译成目标文件
链接:把目标文件和链接器
- 使用宏的优劣势
例如比较两个int 或 double 数值大小,如果用函数,需要写比较 int 的函数、比较 float 的函数, 但如果使用宏的话就只需要写一遍就行了
劣势:
1、每次石永红宏的时候,一份宏定义的代码将插入到程序中,除非宏比较短,否则可能大幅度增加程序程度
2、宏是无法调试的
3、宏是类型无关,也就不够严谨
4、宏可能会带来运算符优先级的问题,导致容易出现错误
- 指针访问
void test4()
{
int aa[2][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9,10};
/**
* 1、&aa 二维数组地址
* 2、&aa +1,跳过整个数组的大小, 也就是数组的最后元素(10)的下一个内存地址
* 3、ptr1 - 1: 指向 10 这个元素的内存地址
* 4、*(ptr1 - 1): 对元素 10 这个内存地址解引用,得到 10
*
*
*/
int *ptr1 = (int *)(&aa + 1);
//ptr1 -1 是指到 10 这个地址 ,*(ptr1 - 1) 解引用10这个地址,得到 10
printf("%d", *(ptr1 - 1));
/**
* 1、aa: 数组名,表示首元素地址,也就是第一行的地址,
* 2、aa +1: 跳转到第 2行地址,*(aa +1)相当于对第2行解引用,拿到了第 2行, 等价于 aa[1],
* 3、*(aa +1): 对第 2 行解引用,相当于是拿到了整个第2行,第 2 行数组名默认是指向首元素地址,
* 也就是 6 的地址, 对 6 这个地址用 * 解引用,得到的是 6
* 4、(int *) (*(aa +1)): 使用 int * 强转没有意义,因为 (*(aa +1)) 本来就是一个整型
* 5、ptr2 -1 : 指向 6 前面一个元素的地址,也就是 5 的地址
* 6、*(ptr2 -1): 对 5 的地址解引用,得到 5
*/
int *ptr2 = (int *)(*(aa + 1));
//输出 5
printf("%d", *(ptr2 - 1));
}
void test1()
{
/**
* 1、a 是一个素组,数组中的每个元素是 char* (”work“中的 "w", "at"中的 a, "alibaba"中的 a )这3个字符的地址放到了 a 中
* 2、char** p = a: a 是数组,默认是指向首元素地址,由于元素类型是 char *, 对 char* 取地址,就是 2级指针,赋值给 har** p 这个二级指针
* 3、pa++: pa 本来是指向 "work"这个字符指针的地址,+!:只想 at这个字符指针的地址
* 4、*pa: 对 pa 解引用,得到的是一级指针,指向 ”at“中的 字符 a 的地址
* 5、%s打印: 打印从 a 开始的字符串, 输出: at
*/
char *a[] = {"work", "at", "alibaba"};
char **pa = a;
// 指向 at 这个字符指针的地址
pa++;
printf("%s\n", *pa);
}
- 字符比较
str1 和 str2 是两个不同的内存空间,因此 str1 和 str2 是不相等的 str3 和 str4 指向的是常量字符串,在内存中只会有一份,因此指向的都是首字符 h的地址
char str1[] = "hello world";
char str2[] = "hello world";
if (str1 == str2)
{
printf("str1 and str2 are the same\n");
}
else
{
printf("str1 and str2 are not the same\n");
}
char *str3 = "hello world";
char *str4 = "hello world";
if (str3 == str4)
{
printf("str3 and str4 are the same\n");
}
else
{
printf("str3 and str4 are not the same\n");
}
- 从数组中查找元素
/**
* 找到了 return 1;找不到 return 0
*/
int find_num(int arr[3][3], int r, int c, int k)
{
int x = 0;
int y = c - 1;
while (x < r && y >= 0)
{
if (arr[x][y] < k)
{
// 往下挪动一行
x++;
}
else if (arr[x][y] > k)
{
y--;
}
else
{
printf("找到了: %d\n", k);
return 1;
}
}
printf("没找到: %d\n", k);
return 0;
}
/**
* 找到了 return 1;找不到 return 0
*/
int find_num2(int arr[3][3], int *px, int *py, int k)
{
int x = 0;
int y = *py - 1;
while (x < *px && y >= 0)
{
if (arr[x][y] < k)
{
// 往下挪动一行
x++;
}
else if (arr[x][y] > k)
{
y--;
}
else
{
*px = x;
*py = y;
printf("找到了: %d\n", k);
return 1;
}
}
printf("没找到: %d\n", k);
return 0;
}
预处理
- 条件编译 满足条件编译,不满足条件不编译
#define DEBUG
/**
* 条件编译,满足某个条件才编译,否则不编译
*/
void conditionCompileTest()
{
printf("start ---->\n");
#ifdef DEBUG
printf("process --->\n");
#endif
printf("end ---->\n");
}
- 常见的条件编译形式
1、
#if 常量表达式
//...
#endif
2、 多个分支的常量表达式
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3、判断是否被定义
#if defined(symbol)
#if !defined(symbol)
#ifdef DEBUG
#ifndef DEBUG
4、嵌套指令
- include <filename.h> 和 "filename.h" 的区别 #include 其实是拷贝一份头文件内容,因此多次引入头文件会导致冗余
1、<> 是引入库文件的, "" 是引入本地文件的
2、使用 "xx.h" 引入时会优先从本地文件查找,找不到去标准库函数中查找,因此如果
函数明确是库函数时,使用 <> 引入的编译效率会更高(省去了从本地文件查找的时间)
- 如何避免头文件被重复引入?
方式一、
test.h
ifndef __TEST_H
defin __TEST_H
int Add(int x, int y)
#endif
方式2(比较现代的写法)
#pragma once
int Add(int x, int y)
- 编译指令 将 test.c编译的产物放到 test.i 文件中
gcc test.c -E > test.i
- 变量相对于地址的偏移
struct s{
char c1;
int a;
char c2;
};
//变量相对于地址的偏移
#define OFFSET(struct_name, member_name) (size_t)&(((struct_name *)0)->member_name)
int main(int argc, char const *argv[])
{
printf("%zu\n",OFFSET(struct s,c1));
printf("%zu\n",OFFSET(struct s,a));
printf("%zu\n",OFFSET(struct s,c2));
return 0;
}