C语言杂谈

在这里插入图片描述
努力扩大自己,以靠近,以触及自身以外的世界

什么是定义?什么是声明?什么是赋值?什么是初始化?

定义:定义就是创建一个对象,为这个对象分配一块内存并给它取上一个名字,这个名字就是我们经常所说的变量名或对象名

声明:告知编译器这个变量名已经被占用,所有的变量声明时不能设置初始值,因为声明时并没有给出存储空间

赋值:给开辟好的空间赋上数据

初始化:一种特殊的赋值,在变量创建的阶段给上数据,初始化只能有一次

// 定义并初始化全局变量
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))

在这里插入图片描述

  • 32
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拖拉机厂第一代码手

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值