c语言进阶

本文深入探讨了C语言的进阶特性,包括变量、表达式、宏、枚举、结构体、联合体、函数、数组与指针、内存分布等。详细阐述了宏的使用风险、函数指针、内联函数、静态函数以及extern的作用。同时,介绍了结构体的对齐规则、联合体的存储机制以及数组和指针的关系。文章还讨论了不同数据类型的大小、内存布局和内存分配,对于理解C语言的底层工作原理非常有帮助。
摘要由CSDN通过智能技术生成


32位系统

char 1
short 2
int 4
long 4
float 4  无符号
double 8 无符号
const * 左定值
* const  右定项

变量

在这里插入图片描述
在这里插入图片描述

表达式

--,++只能用于变量
---
sizeof 返回无符号
有符号和无符号运算,首先自动转成无符号

在这里插入图片描述
在这里插入图片描述

int m=2,n=3;
int k = m+++n;//先用m再自加
m=3,n=3,k=5;
int m=5;
int n=3;
int k=m+++-+-+--n;
m=6,n=2,k=(m++)(+)(-)(+)(-)(+)(--n)=7

1.常量替换
#define MAX_CLASS 10

2.类型定义
#define INTEGER int

3.宏函数(参数需要用()括起来)
容易引发优先级低的问题
#define MAX(x,y) (((x)>(y))?(x):(y))

4.防止头文件重复包含
#ifndef TEST_H
#define TEST_H
#include "xxx.h"
#ifdef _cplusplus
//下面的代码是用c语言进行声明和定义的
//如果不加,那么头文件的编译方式由include此头文件的源文件决定
extern "C"{
#endif
//头文件内容
#ifdef _cplusplus
}
#endif
#endif

5. 内定调试宏
_LINE_:当前代码行数
_FILE_:当前源文件名
_DATE_:当前日期
_TIME_:当前时间

宏风险

1. 容易引发优先级低的问题
参数需要用()括起来

2.多次运算
参数为函数的时候

3.同一个宏名字多处定义

4.宏空格
#define func (x) (x+1)

在这里插入图片描述

宏函数运行顺序

#define PT = 3.5;
#define S(x) = PT * x * x;
S(1+2) = 3.5*1+2*1+2=7.5//这才是要加括号的原因

枚举

枚举的大小和编译器,编译选项有关

建议:
1. 不要使用枚举的大小(与编译器有关)
2. 枚举的值不要超过32位

在这里插入图片描述
在这里插入图片描述

用法

用于声明一组常数。当一个变量有几个固定的可能取值时,可以将这个变量定义为枚举类型

enum Season {spring, summer, autumn, winter} s;
s = spring; // 等价于 s = 0;
s = 3; // 等价于 s = winter;

结构体

结构体对齐

1. 运行速度更快
2. 指针从基地址读取数据可能出错
3. 结构体直接赋值可能会出错

结构体大小

//20
typedef struct{
   int (*a[5])();
}Foo1;
//8
typedef struct{
   int (*a)[5];
   char b;
}Foo2;
//20
typedef struct{
   int a[5];
}Foo3;
//20
typedef struct{
   int *a[5];
}Foo4;
typedef struct{
	u8 a;
	u8 b;
	u8 c;
}TEST;
TEST = test{1,2,3};
u16 usShort;
u32 ulInt;
usShort = *(U16*)&test.a;
usShort = *(U16*)&test.b;
ulInt = *(U32*)&test.c;
是否出错和cpu以及编译器相关

在这里插入图片描述
解决方法

1.自然对齐
	(1)成员按照8,4,2,1顺序排列
	(2)不够对齐的字段用保留字段填充
2.预编译对齐 #pragma pack(n)
3.不对齐结构不用非同类型指针访问,不进行类型转换
4.尽量不对结构体进行直接赋值,使用memcpy拷贝整个结构体

如何对齐

1.以自身最大的为自身对齐值
2.自身对齐值与指定对齐值中的小值

注意:

1.0长度的数组不占用空间
b[0],b[]:直接通过b访问后面结构体内存

联合体

同一个内存空间存储不同的数据类型

小端模式
高字节在高地址,低字节在低地址

联合体赋值时,短数据赋值之后,长数据的空余位数据未知

基本用法

typedef union
{
	unisigned int a;
	unisigned int b;
}TEST_U1;
当u1.a = 0x12345678;
u1.b = 0x12345678;
typedef struct
{
	unisigned char a;
	unisigned char b;
	unisigned char c;
	unisigned char d;
}TEST_S3;
typedef union
{
	unisigned int a;
	unisigned char b;
	unisigned short c;
}TEST_U2;
当 u2.a = 0x12345678
u2.b = 0x78;
u2.c = 0x5678;
typedef union
{
	unisigned int a;
	TEST_S3 b;
}TEST_U3;

在这里插入图片描述

当 u3.a = 0x12345678;
u3.b.a = 0x78;
u3.b.d = 0x12;

联合体大小

最大字段的大小

函数

函数定义声明

int Tesy1();
void Test(int num,char array[]);
函数指针
typedef int (*PFUN)();
PFEUN pf = Test1;

函数入参

在这里插入图片描述

1 4 4 4
全是指针,数组的定义其本质都是指针

inline函数

目的

a)没有调用开销,效率高
b)是真正的函数,编译器会监测参数类型,消除宏函数隐患
c)太复杂或调用点太多,展开后会导致代码膨胀带来的恶化可能大于效率提升带来的益处
a)只是对编译器的建议,编译器可以忽略
b)在调用内联函数时,要保证内联函数的定义让编译器看到,即在头文件中定义内联函数,这与通常的函数定义不一样

static函数

static int Test1();

目的

a)限定作用域,只能被本文件中其他函数调用,不能被同一程序其他文件中的函数调用

extern函数

extern int Test1();

目的

a)函数和变量前,表示变量或函数的定义在其他文件中,
  提示编译器遇到此变量和函数字其他模块中寻找定义
b)取代include "*.h"来声明函数
c)extern "C"
   C++在编译时为解决函数多态问题,会讲函数名和参数联合起来生成一个中间的函数名,
   而c语言不会,因此会导致链接时找不到对应函数的情况。

在这里插入图片描述
链接: link.

数组与指针

指针

char ch = 'c';
char *pch = &ch;
char *pStr = "abc";//pStr[1] = b (*pStr+2)

int m;
int *pm = &m;

struct Test stTest;
struct Test *pst = &stTest;

//函数指针
extern void TestFunc(int m);
void (*pf)(int) = TestFunc;

int *(*a[5])(int, char*);  
=
typedef int* (*f)(int,char*);
f a[5];
a是个5个元素的数组,每个元素为函数指针.

函数指针

#include <stdio.h>
#include <stdlib.h>
 
int* (*a[5])(int,char*);
 
int *foo(int n, char *s)
{
	int *p;
 
	p = (int *)malloc(sizeof(int));
	*p = n + atoi(s);
 
	return p;
}
 
int main(int argc, char *argv[])
{
	int *p;
 
	a[0] = &foo;
	p = (*a[0])(1, "2");
 
	printf("%d\n", *p);//3
 
	return 0;
}

链接: link.

指针的大小

   int ll[6];
   //注意*ll[3] = ll[3];int数组
   //数组指针
   int (*l)[3]=&ll;
   int p[5];
   printf("%d\n",sizeof(l));
   printf("%d\n",sizeof(*l));
   printf("%d\n",sizeof(p));
   return 0;

结果
在这里插入图片描述

指针数组的首地址长度是4;
指针数组取地址是指针数组的大小12;
数组则是指定的大小20;

指针类型转换

   int a[5] = {1,3,5,7,9};
   char *p = a;
   int *t = (int *)p;
   printf("%d %d %d %d",*p,*(p+sizeof(int)),*(++t));

结果
在这里插入图片描述
不可

((int*)p)++;

在 C 语言中, 类型转换意味着 ``把这些二进制位看作另一种类型, 并作相应的对待"; 这是一个转换操作符, 根据定义它只能生成一个右值 (rvalue)。而右值既不能赋值, 也不能用 ++ 自增。(如果编译器支持这样的扩展, 那要么是一个错误, 要么是有意作出的非标准扩展。)

数组

char m[10];
char m[] = "123";
char m[] = {'1','2'};
int m[3][2];
int *k[10];
//自带了/0所以是18+1
char ac[] = "welcome\0to\0huawei\0";
//strlen到\0结束为7
printf("%d %d\n",sizeof(ac),strlen(ac));

结果
在这里插入图片描述

int c[4][2];//0x100;
(c+1) = 0x108;//步长为2 int 8
(*(c+1)+1) = 0x10c  //8 + 4

声明一个函数,返回二维数组,int,最右空间为10.

int (*Func())[10];

数组为参数时,下面的都是一样的

char p[10];
char p[];
char *p;
char p[10][2];
char (*p)[2];
char p[][2];
char test_5[] = "123";
sizeof(test_5) = 4//包含'\0'

内存分布

在这里插入图片描述

printf(%s\r\n,dataList);
1. 字符数组局部变量,返回后变量内存收回 。\?
2. 字符数组局部变量,返回后变量内存收回。 \?
3. 字符串常量,全局存在。abc
4. 静态变量,作用域只在函数内,空间和全局变量一致。abc
int a = 0x0506;//全局变量初始化,数据段
char *p0 = "this is test string";;//全局变量初始化,数据段
char *p1;//全局变量未初始化 bss段
int main()
{
	iny b;//栈
	char s[] = "abc";//栈
	char *p2;;//栈
	char *p3 = "123";//123在常量区,p3在栈上
	static int c =0x0203;//全局(静态)初始化区
	static char *str1 = "abcd";//全局(静态)初始化区
	p2 = (char*)malloc(10);//堆
	strcpy(p2,"5678");
	free(p2);
	return 0;
}
字符串在数据段还是在代码段由编译器决定
int gy;//初始化为0
void Foo(){
	int x;//未知
	int *p;
	for(int i;i<10;i++){
		p = &i;//出了这个大括号i无效化
	}
   
	printf("%d %d %d %d",gy,x,*p,i);//报错
   return;
}

BSS段:存放程序中未初始化的全局变量,不占用执行程序大小,其内容由操作系统初始化(清零)。
数据段:存放程序中已初始化的全局变量。
代码段:存放程序执行代码的内存区域,大小在程序运行前已经确定,并且通常属于只读。
堆:存放进程运行中被动态分配的内存段,大小并不固定,可动态扩张或缩减,马路咯从分配的为此内存。
栈:用户存放程序临时创建的局部变量(不包括static声明的变量)。在函数被调用时,其参数也会被压入发起调用的进程栈。

void Test(){
	static char *str = "abc";
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值