努力扩大自己,以靠近,以触及自身以外的世界
文章目录
- 什么是定义?什么是声明?什么是赋值?什么是初始化?
- 什么是生命周期?什么是作用域?全局变量?局部变量?
- sizeof是函数吗?关键字!!!
- signed、unsigned 关键字
- static关键字
- abs函数和fabs函数
- 空结构体占多大空间?
- 柔性数组
- union
- enum
- typedef
- 取整和取模
- 内存对齐——为什么需要内存对齐?
- 宏定义#define
- 指针和数组的关系?
- 向特定地址中写入数据?
- #和##
- assert是宏而不是函数
- malloc申请0字节空间
- 函数参数的传递发生在函数调用之前
- 可变参数列表
什么是定义?什么是声明?什么是赋值?什么是初始化?
定义:定义就是创建一个对象,为这个对象分配一块内存并给它取上一个名字,这个名字就是我们经常所说的变量名或对象名
声明:告知编译器这个变量名已经被占用,所有的变量声明时不能设置初始值,因为声明时并没有给出存储空间
赋值:给开辟好的空间赋上数据
初始化:一种特殊的赋值,在变量创建的阶段给上数据,初始化只能有一次
// 定义并初始化全局变量
int global = 10;
// 函数声明
void myFunction();
int main()
{
// 声明并初始化局部变量
int local;
local = 20;
// 调用函数
myFunction();
// 打印全局变量和局部变量的值
printf("Global: %d\n", global);
printf("Local: %d\n", local);
return 0;
}
// 函数定义
void myFunction()
{
// 赋值操作
global = 30;
}
什么是生命周期?什么是作用域?全局变量?局部变量?
生命周期:从开辟到释放所经历的这一时间段
作用域:变量的有效作用范围
全局变量:在整个程序的任何地方都是可用和可访问的
局部变量:只能在变量特定的作用域内起作用
// 全局变量,定义在函数外部,可以在整个程序中使用
int global = 10;
// 函数定义
void myFunction()
{
// 局部变量,定义在函数内部,只能在函数内部使用
int local = 20;
// 访问全局变量和局部变量,并打印它们的值
printf("Global: %d\n", global);
printf("Local: %d\n", local);
}
int main()
{
// 调用函数
myFunction();
// 尝试访问局部变量,会导致编译错误
// printf("Local in main: %d\n", local);
// 访问全局变量
printf("Global in main: %d\n", global);
return 0;
}
sizeof是函数吗?关键字!!!
说来惭愧,当听到问sizeof是函数吗?第一时间就想到它后面接的是(),理所当然的认为sizeof就是函数…
可是sizeof也可以不加()使用啊
int main()
{
int val = 100;
printf("sizeof() : %d\nsizeof : %d\n", sizeof(val), sizeof val);
return 0;
}
但是!!!sizeof 在计算变量所占空间大小时,括号可以省略,而计算类型(模子)大小时不能省略。
signed、unsigned 关键字
直接上代码
int main()
{
char a[1000];
int i;
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a));
return 0;
}
乍一看,很简单,再一看,嘶~~~好像要思考一下,看到负数就想到负数在计算机中的存储形式,以补码的形式存储,最高位符号位为1。char类型占1字节即8比特位,所以是从-128~127一共256个数,但是strlen是以\0为结尾,所以一共255个数。
正数的原码反码补码都是一样,没什么好说,而负数的存储是以补码的形式存储,所以负数的存储首先就需要将源码转换为补码,然后在将其存入到内存中。注意!!!!就是这么一个过程,先转换,然后存入。所以我数据的存储是不关注你存放在哪里,存放好之后我能够读取出来就行,所以signed和unsigned两种类型的区别就是我是否关注符号位,然后进行不同的读取。
正数负数我该怎么存就怎么存,有无符号是你读取的方式,读出来多少是你的事
再来一段代码
int i = -20;
unsigned j = 10;
i+j 的值为多少?为什么?
此时的结果随着你读取的方式而变化,如果使用printf(“%d”, i + j)的话,结果为-10。当使用printf(“%u”, i + j)的话,结果为42亿多。
static关键字
static关键字在修饰变量时有两种情况:修饰全局变量,修饰局部变量
修饰全局变量:被修饰的全局变量也称静态全局变量,改变了该全局变量的作用域,使得该变量只在声明它的源文件中可见,而在其他源文件中是不可见的
修饰局部变量:生命周期扩展到整个程序的执行期间,但作用域仍限于声明它的函数内部,整个执行期间只初始化一次,且默认为0
修饰函数:函数的作用域限定在声明它的文件内部,使得该函数对于其他文件是不可见的
void function()
{
static int x; // 静态变量
x++;
printf("x: %d\n", x);
}
abs函数和fabs函数
abs用于整形的绝对值,fabs用于浮点型的绝对值
空结构体占多大空间?
struct empty
{
};
int main()
{
empty emp;
printf("empty struct size : %d\n", sizeof(emp));
return 0;
}
一般而言空结构体的大小是给1字节,但是具体是多少还是依编译器。编译器认为任何一种数据类型都有其大小,用它来定义一个变量能够分配确定大小的空间。(vs中直接报错…)
柔性数组
在C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结构中的柔性数组成员前面必须至少有一个其他成员,且一个结构体只能有一个柔性数组。
柔性数组在定义时动态开辟空间,不影响结构体的大小
struct array1
{
int lenth;
int arr[];
};
struct array2
{
int lenth;
int *arr;
};
int main()
{
struct array1 a;
struct array2 b;
printf("size : %d\n", sizeof(a));
printf("size : %d\n", sizeof(b));
return 0;
}
union
union也称联合体或者共用体,顾名思义,就是联合体内所有数据共用一块内存,这块内存大小是成员类型最大的字节数。
可以用union来验证大小端
union U
{
int a;
char b;
};
int main()
{
union U u1;
u1.a = 1; //0x0001;
if (u1.b == 1)
{
printf("小端机\n");
}
else
{
printf("大端机\n");
}
return 0;
}
enum
enum枚举类型允许对一批整形变量进行命名,提高代码可读性
不需要实例化对象
enum Weekday {
Monday = 1,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
};
sizeof枚举类型的大小是多少?
enum类型的大小并不是固定的,enum类型的大小是由编译器来决定的,可能是4字节也可能是8字节,并且还与你给出的值有关
enum Week //demo1
{
day1 = 0x11223344,
day2,
day3
};
// sizeof(enum Week) = 4
enum Week //demo2
{
day1 = 0x1122334455,
day2,
day3
};
// sizeof(enum Week) = 8
typedef
给一个已经存在的数据类型(注意:是类型不是变量)取一个别名,而非定义一个新的数据类型
注意:typedef在给类型取别名时,完全继承了原始类型的属性,但是不能和其他类型修饰符进行组合使用来修改这些属性
typedef int int32;
int main()
{
// unsigned int32 a = 0; // 错误
int32 b = 1;
}
取整和取模
整数除法中,C语言的行为规则是向零取整
当求模运算中至少一个操作数为负数时,C语言的求模运算结果的符号由被除数的符号决定。
内存对齐——为什么需要内存对齐?
简要的说,数据按照特定的规则放到对应的地址上就是内存对齐,内存对齐可以增强系统性能,因为对于没有对齐的数据,操作系统读取数据可能需要多次的内存访问,而内存对齐后数据就在对齐边界上,操作系统一次内存访问就可以读取数据,提高性能
宏定义#define
宏定义的常量或者宏函数都是在预处理阶段直接进行机械替换,因此使用宏函数可以免去函数调用的开销,提高性能。但是定义的这些都没有类型安全检查,并且存在优先级问题,需要谨慎使用
宏的作用域:宏的作用域是在其定义的地方向后
void func1()
{
int num = M; //不替换
}
int main()
{
#define M 10
int num = M; //替换
func1();
func2();
return 0;
}
void func2()
{
int num = M; // 替换
}
指针和数组的关系?
指针和数组没有关系!!!指针的大小为4/8字节,数组的大小为(类型 * 数据)
指针存放地址,该地址是数据存放的地址。数组存放数据,只是数组名类似于指针,是数组中第一个元素的地址
指针是一个变量,可以指向任何数据类型,而数组是一个固定长度的数据集合
指针可以被重新赋值指向不同的内存地址,而数组名则不能被重新赋值
向特定地址中写入数据?
指针指向某个地址,如果权限允许,我们可以向该地址中写入数据。一般来说关注的都是数据而不是地址,当想向特定地址写入数据的话那该怎么做呢?
假设向0x12ff7c的地址中写入数据
int main()
{
int *p = (int*)0x12ff7c;
*p = 10;
//又或者 *(int*)0x12ff7c = 10;
return 0;
}
#和##
#
号:在宏定义中,#
号用于将参数转换为字符串字面值。这个过程称为字符串化。当#
号放在宏参数前面时,它将该参数转换为一个以双引号包围的字符串字面值
#define STRINGIZE(x) #x
printf("%s\n", STRINGIZE(hello)); // 将输出 "hello"
##
号:在宏定义中,##
号用于连接两个标识符或符号
#define CONCAT(x, y) x##y
int ab = 10;
printf("%d\n", CONCAT(a, b)); // 将输出 10
##
号只能用于连接标识符或符号,不能用于连接字符串或数字
assert是宏而不是函数
_ACRTIMP void __cdecl _wassert(
_In_z_ wchar_t const* _Message,
_In_z_ wchar_t const* _File,
_In_ unsigned _Line
);
#define assert(expression) (void)( \
(!!(expression)) || \
(_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) \
)
assert只存在于debug版本中,不存在release版本中,assert的作用是定位错误,而不是排除错误
malloc申请0字节空间
申请0字节函数返回的是正常地址,因为函数的返回值规定了返回NULL代表申请失败,而申请0字节是成功的,但是返回的地址是不可以使用的,强制使用会导致未定义行为
malloc除了给到你申请的空间外,还会额外给你更多的空间来存放元信息
函数参数的传递发生在函数调用之前
int addNum(int num1, int num2)
{
return num1 + num2;
}
int main()
{
int ret = addNum(1, 2, 3, 4);
printf("ret = %d\n", ret);
return 0;
}
在C语言中,如果函数没有参数,那么对该函数进行传参也是可以,因为参数传递发生在函数调用之前
void Empty()
{}
int main()
{
Empty(1, 2, "123");
return 0;
}
可变参数列表
使用C语言的可变参数列表需要包含<stdarg.h>
这个头文件,里面包含有几个宏,例如va_start、va_arg、va_end和va_copy,大致的使用流程:
#include <stdio.h>
#include <stdarg.h>
Myadd(int n, ...)
{
va_list args; //1. 定义va_list类型变量
va_start(args, n); //2. 初始化args变量
int sum = 0;
for (int i = 0; i < n; i++)
{
sum += va_arg(args, int); //去除参数
}
va_end(args); //args置空
return sum;
}
int main()
{
int ret1 = Myadd(4, 1, 2, 3, 4);
int ret2 = Myadd(3, 2, 3, 4);
printf("ret1=%d ret2=%d\n", ret1, ret2);
return 0;
}
其中va_list是typedef的,原型为char*
其他几个都是宏函数
//这个宏通常用于确定参数在堆栈上的对齐方式。
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define _ADDRESSOF(v) (&(v))
#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
//这个宏通常用于确定参数在堆栈上的对齐方式。
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define _ADDRESSOF(v) (&(v))
#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))