左值:
左值是可标识且可寻址的表达式,它可以出现在赋值操作符的左边。换句话说,左值表示一个具体的内存位置。例如,变量、数组元素和对象成员都可以是左值。
右值:
右值是指不能寻址的表达式,它只能出现在赋值操作符的右边。右值可以是字面量、临时对象和表达式的结果。右值是临时创建的,它们没有固定的内存位置。
区别:
左值表示可标识且可寻址的表达式,而右值表示不能寻址的临时表达式。
值传递:
值传递是一种参数传递的方式,其中将参数的值复制给函数的形式参数。在值传递中,原始参数的值在函数调用期间被复制到新的内存位置,而函数中使用的是这个副本。
当函数使用值传递方式调用时,对形参的修改不会影响到原始参数。这是因为函数内使用的是参数的副本,而不是原始参数本身。这也意味着在函数内部对参数的修改在函数外部是不可见的。
值传递的主要优点是简单,容易理解和使用。它不会影响到原始参数,因此可以避免意外的副作用和数据污染。此外,由于使用的是参数的副本,函数的调用方和被调用方之间相互独立,不会相互干扰。
然而,值传递也有一些缺点。首先,复制参数的值可能会消耗一定的时间和内存。如果参数是较大的对象或数据结构,则复制的开销可能会很大。其次,对于需要在函数中修改原始参数的情况,值传递无法实现这一目标,因为函数只能修改副本。
总之,值传递是一种简单和安全的参数传递方式,适用于不需要修改原始参数且参数较小的情况。但对于大对象或需要修改原始参数的场景,其他参数传递方式(如引用传递或指针传递)可能更合适。
地址传递:
地址传递是一种参数传递的方式,其中函数接收参数的内存地址而不是参数的值本身。通过地址传递,函数可以直接访问和修改原始参数所在的内存位置。
在地址传递中,函数的形式参数是指针类型,它存储了原始参数的内存地址。通过解引用操作符(*)或指针操作符(->),函数可以访问和修改原始参数的值。
使用地址传递的主要优点是可以实现对原始参数的修改。由于函数直接操作原始参数所在的内存位置,所做的修改在函数外部是可见的。这种特性对于需要修改原始参数的情况(如交换两个值、修改数组等)非常有用。
此外,地址传递避免了复制参数的值所带来的开销,特别是对于大型对象或数据结构。通过传递地址,函数可以直接访问原始参数的内容,而不需要进行复制。
然而,地址传递也有一些注意事项。首先,需要确保在函数中不会意外地修改原始参数的值,以避免引入潜在的错误和副作用。其次,使用地址传递可能增加代码的复杂性和难以理解性。
总结来说,地址传递是一种可以实现对原始参数修改的参数传递方式,适用于需要修改原始参数的情况以及对参数的复制开销敏感的场景。但需要注意在函数内部正确处理和保护原始参数的值。
值传递1:
#include
void swap(int m,int n)
{
int temp;//交换m.n的中间变量
temp=m;
m=n;
n=temp;
printf("m=%d,n=%d\n",m,n);
}
int main()
{
int a=12;
int b=90;
swap(a,b);//传实参a.b的值给形参m.n相当于复制一份给形参,并且调用swap函数
printf("a=%d,b=%d",a,b);
值传递2:虽然传的地址但是没有交换地址里的内容
#include
void fun(int *p,int *q)//用指针接收
{
int *temp;//定义一个指针交换变量
temp=p;//交换p和q的地址
p=q;
q=temp;
printf("*p=%d,*q=%d\n",*p,*q);
}
int main()
{
int a=90;
int b=98;
fun(&a,&b);//传a.b的地址
printf("a=%d,b=%d\n",a,b);
}
地址传递:
#include
void fun(int *p,int *q)//用指针接收
{
int temp;//定义一个交换变量
temp=*p;//交换p和q的地址里的内容/值
*p=*q;
*q=temp;
printf("*p=%d,*q=%d\n",*p,*q);
}
int main()
{
int a=90;
int b=98;
fun(&a,&b);//传a.b的地址
printf("a=%d,b=%d\n",a,b);
}
返回值:
#include
int fun()
{
int var=999;
return var;
}
int main()
{
int ret=fun();//值的返回只能作为右值
printf("hun()=%d\n",fun());//999
}
地址返回
#include
int *fun()
{
static int var=999;//静态变量虽然在函数体内定义,但是不占函数的内存空间
return &var;
}
int main()
{
int *p=fun();//地址返回可以是左值也可以是右值
*fun()=555;
printf("fun()=%d\n",*fun());//555
printf("*p=%d\n",*p);//555
return 0;
}
0-3G用户空间,代码操作空间
3-4G内核空间,和底层驱动打交道
内存分区(Memory Partitioning)是计算机系统中将内存划分为不同区域的过程。每个分区都有特定的用途和限制,用于存储不同类型的数据和执行不同的任务。
常见的内存分区包括以下几种:
1. 代码段(Code Segment):也称为文本段,用于存储程序的机器指令。该段通常是只读的,因为程序的指令在运行时不会被修改。
2. 数据段(Data Segment):用于存储全局变量和静态变量。数据段可以分为初始化数据段和未初始化数据段。初始化数据段中存储已经初始化的变量,而未初始化数据段中存储尚未初始化的变量。
3. 堆(Heap):用于动态分配内存。堆中的内存可以根据需要在运行时进行分配和释放。开发人员可以使用堆上的内存来存储动态分配的对象和数据结构。
4. 栈(Stack):用于存储函数调用的相关信息,包括局部变量、函数参数和函数返回地址。栈上的内存是自动分配和释放的,遵循先进后出(LIFO)的原则。
5. 常量区(Constant Area):用于存储常量数据,例如字符串常量。常量区的数据通常是只读的,无法进行修改。
6. 文件映射区(Memory-mapped File):用于将文件映射到内存中,以便将文件内容视为内存的一部分进行读写操作。
每个内存分区都有其特定的访问权限和使用规则。例如,代码段和常量区是只读的,而堆和栈可以进行读写操作。
#include
int m;//未初始化的全局变量.bss段
int n=520;//初始化的全局变量.date段
static int k;//未初始化的静态变量.bss段
static int n=666;//初始化的静态变量.date段
char arr[10]="hello";//arr数组在.date段而里面的内容在ro段
char *p="hello";//指针在.date段,里面的内容在ro段int main()
{
double b=999.0;//局部变量在栈区申请
int q;//局部变量在栈区申请,初始值为随机值
static int c;//静态局部变量,在.bss段申请
static int d=510;//静态局部变量,在.date段
char *p="eeee";//p在栈区申请的8字节,里面的内容在ro段
char e[100]="kkkk";//数组在栈区申请,里面的内容在ro段
int *ptr=(int*)malloc(sizeof(int));//ptr在栈区申请,而申请的空间在堆区
return 0;
}
在C语言中,"static" 关键字可以用于以下几个方面:
1. 静态变量(Static Variables):在函数内部或在文件作用域中声明的变量可以使用 "static" 关键字进行修饰。静态变量在函数内部的作用域仅限于声明它的函数,并且它的生命周期会延长到整个程序的运行期间。在文件作用域中,静态变量被限定在定义它的源文件中,其他源文件无法直接访问该变量。
2. 静态函数(Static Functions):在函数声明前面加上 "static" 关键字,可以将函数声明为静态函数。静态函数的作用域被限制在定义它的源文件中,在其他源文件中无法直接调用该函数。静态函数通常用于隐藏实现细节,仅供内部使用。
3. 静态全局变量(Static Global Variables):在全局作用域中声明的变量可以使用 "static" 关键字进行修饰,形成静态全局变量。静态全局变量的作用域仅限于定义它的源文件,在其他源文件中无法直接访问该变量。与普通的全局变量相比,静态全局变量的可见范围更受限制,可以减少命名冲突和不必要的全局变量的暴露。
4. 静态结构成员(Static Structure Members):在结构体中声明的成员可以使用 "static" 关键字进行修饰,形成静态成员。静态结构成员属于整个结构体类型,而不是结构体的具体实例。静态成员在结构体的不同实例之间是共享的,并且可以通过结构体类型直接访问。
需要注意的是,虽然 "static" 关键字在不同的编程语言中可能有类似的概念,但其具体语义和用法仍可能有所不同。在C语言中,"static" 主要用于限制变量和函数的作用域和可见性,并控制其生命周期和链接性。在具体的编程项目中使用 "static" 关键字时,请根据C语言的规范和要求进行正确使用。
在C语言中:初始化过的变量就不能修改了,所以只读,所以在ro段
melloc .free是对堆区空间操作
原型:void *malloc (size_t size)
功能:手动从堆区申请空间
参数:申请空间大小 ,以字节为单位,一般为size(类型名)*n
返回值:万能指针,可以强转为自己想要的类型,如果申请成功将从堆区申请的空间地址返回,如果申请失败返回NULL
原型:void free(void *ptr);
功能:从堆区申请的空间
参数:要释放的空间首地址
返回值:无
单个空间内存的申请:数据类型 *指针名 = (数据类型*)malloc(sizeof(数据类型)));
连续内存的申请: 数据类型 *指针名 = (数据类型*)malloc(sizeof(数据类型)*n)); 释放:free(指针名;
#include
#include//malloc所在的头文件 int main(int argc, const char *argv[])
{
//在堆区申请一个int类型的空间大小
int *p1 = (int *)malloc(4); //申请4字节的大小
printf("*p1 = %d\n", *p1); //随机值int *p2 = (int *)malloc(sizeof(int)); //申请一个int类型的大小
*p2 = 520; //给堆区空间进行赋值
printf("*p2 = %d\n", *p2); //520
//连续申请5个int类型的大小
int *p3 = (int *)malloc(sizeof(int)*5);
//输出默认值
for(int i=0; i<5; i++)
{
//printf("%d\t", p3[i]);
printf("%d\t", *(p3+i));
}
printf("\n");
//释放堆区空间
free(p1);
p1 = NULL; //防止野指针
free(p2);
p2 = NULL;
free(p3);
p3 = NULL;
return 0;
}
练习:要求在堆区申请6个int类型空间存放6名学生的成绩,分别使用函数实现申请空间、输入学生成绩、输出学生成绩、对学生进行升序排序、释放空间
#include
#include
#include
void out_(int *p, int n)
{
int i;
for(i=0;i{
printf("%-4d",*(p+i));
}
printf("\n");
}
//定义冒泡排序函数
void sort_(int *p, int n)
{
int k,j,temp=0;
//printf("排序后:\n");
for(k=1;k{
for(j=0;j{
if(*(p+j) > *(p+j+1)) //大升小降
{
//交换三部曲
temp = *(p+j);
*(p+j) = *(p+j+1);
*(p+j+1) = temp;
}
}
}printf("排序成功\n");
}//定义从堆区申请空间的函数,num表示要申请空间的个数
int *memory(int num)
{
//从堆区申请num个int类型的大小空间
int *ptr = (int *)malloc(sizeof(int) * num);//判断是否申请成功
if(NULL == ptr)
{
printf("内存申请失败\n");
return NULL;
}else
{
printf("申请成功\n");
return ptr;
}
}
//定义输入函数,ptr表示指向堆区空间的地址,num表示堆区空间元素个数
void input(int *ptr, int n)
{
if(NULL != ptr)
{
//开始进行输入工作
for(int i=0; i{
printf("请输入第%d个学生的成绩:",i+1);
scanf("%d", ptr+i);
}
}
printf("成绩录入完毕\n");
}//定义释放空间函数
void myFree(int *ptr)
{
if(NULL != ptr)
{
free(ptr);
ptr = NULL;
}
}
int main(int argc, const char *argv[])
{
//int *p1 = (int *)malloc(sizeof(int)*6);
int *p1 = memory(6); //调用申请空间函数,在堆区申请6个int大小的空间input(p1, 6); //调用输入函数完成成绩的录入功能
sort_(p1, 6); //调用排序函数
out_(p1, 6); //调用输出函数//释放空间
myFree(p1);
p1 = NULL;
return 0;
}
类型重定义本质上是给类型重新起个名字,使得代码更加易于理解,例如:将unsigned long int 改名为uint64,将unsigned short int 改名为uint16
typedef 数据类型名 新名;
1> 使用变量定义类型
int a; //定义普通变量 i
nt *ptr; //定义指针类型变量
int arr[5]; //定义数组类型变量
int *ptr_arr[5]; //定义指针数组变量
int (*arr_ptr)[5]; //定义数组指针变量
int (*fun_ptr)(int,int); //定义函数指针变量
int (*fun_ptr_arr[3])(int, int); //定义函数指针数组的指针变量
int **pptr; //定义二级指针变量
int ; //定义普通数据类型
int *; //定义指针类型
int [5]; //定义数组类型
int *[5]; //定义指针数组
int (*)[5]; //定义数组指针
int (*)(int,int); //定义函数指针
int (*[3])(int, int); //定义函数指针数组的指针
int **; //定义二级指针
typedef int A; //重定义int类型为A类型
typedef int *Ptr; //重定义指针类型
typedef int ARR[5]; //重定义数组类型
typedef int *Ptr_Arr[5]; //重定义指针数组
typedef int (*Arr_Ptr)[5]; //重定义数组指针
typedef int (*Fun_Ptr)(int,int); //重定义函数指针
typedef int (*Fun_Ptr_Arr[3])(int, int); //重定义函数指针数组的指针
typedef int **PPtr; //重定义二级指针
#include
#includetypedef unsigned short int uint16; //将无符号短整形重命名为uint16
typedef int * Ptr_i; //将int*类型重命名为Ptr_i
typedef char String[10]; //将char [5]类型重命名为String
int main(int argc, const char *argv[])
{
uint16 num = 520; //等价于unsigned short int num = 520
printf("sizeof num = %ld\n", sizeof(num)); //2
printf("num = %d\n", num); //520Ptr_i p1 = NULL; //此时p1是指针变量 int *p1;
printf("sizeof p1 = %ld\n", sizeof(p1)); //8String s1; //此时是定义的长度为10的字符数组
strcpy(s1, "hello");
printf("s1 = %s\n", s1); //hello
printf("sizeof s1 = %ld\n", sizeof(s1)); //10
printf("strlen s1 = %ld\n", strlen(s1)); //5
return 0;
}
给类型起多个名·
#include
typedef int *Ptr_i, int32; //int32是一个int类型的重命名
//Ptr_i是int*类型的重命名
int main(int argc, const char *argv[])
{
int32 num; //num是一个普通变量
Ptr_i p; //p是一个int×类型的变量printf("sizeof num = %ld\n", sizeof(num)); //4
printf("sizeof p = %ld\n", sizeof(p)); //8
return 0;
}
#include
#define ptr_i int* //会将uint32替换成int
typedef int * pptr_i; //类型重定义
int main(int argc, const char *argv[])
{
ptr_i a,b; //a是指针类型,b是普通int类型
int *a, b;
pptr_i m,n; //m和n都是指针类型
printf("sizeof a = %ld\n", sizeof(a)); //8
printf("sizeof b = %ld\n", sizeof(b)); //4
printf("sizeof m = %ld\n", sizeof(m)); //8
printf("sizeof n = %ld\n", sizeof(n)); //8
return 0; }
系统提供的数据类型不够用了,没有条件创造条件,自己定义数据类型,自己用
有相同数据类型和不同数据类型构成的集合叫结构体,属于构造数据类型
struct 结构体名
{
//属性列表
成员类型1 成员变量1;
成员类型2 成员变量2;
。。。
成员类型n 成员变量n;
};注意:
1、struct是定义结构体的关键字
2、结构体名:是一个标识符,要符合标识符的命名规则,一般首字母大写
3、所有的成员属性使用一对花括号包裹,最后用分号结束,分号不能省略
4、成员属性类型可以是基本数据类型,也可以是构造数据类型
5、声明结构体不占内存空间,使用结构体类型定义变量时,变量会占用内存空间
6、一般将声明结构体放在文件头部或者头文件中,也可以声明在其他部分,但是,至少要声明在使用之前
//声明一个英雄结构体类型
struct Hero
{
char name[20]; //姓名
int Hp; //血量
double speed; //基础位移
int kill; //人头数
};
#include
//声明一个英雄结构体类型 struct Hero
{
char name[20]; //姓名
int Hp; //血量
double speed; //基础位移 i
nt kill; //人头数
}h3 = {"盖伦", 3500, 500, 5};
struct //无名结构体
{
char name[40]; //名称
char position[40]; //产地
double price; //单价
double weight; //重量
}g1 = {"三鹿奶粉", "China", 350, 1000};
int main(int argc, const char *argv[])
{ //使用英雄类型定义一个英雄变量
struct Hero h1 = {"亚索", 650, 350, 0};
//此时定一个英雄变量h1
//定义英雄变量,指定某个成员进行赋值
struct Hero h2 = {.Hp=2000, .speed = 1000}; return 0; }
1> 成员运算符 ".",个人读作"的";例如h1.name 表示h1结构体变量的name成员
2> 不要企图通过结构体变量名直接输出所有成员,如果想要输出结构体变量中的成员,需要使用成员运算符一个一个找
3> 相同类型的结构体变量之间是可以之间互相赋值的
4> 也可以对结构体变量取地址运算,其地址跟第一个成员属性的地址保持一致
1> 结构体指针访问成员使用运算符"->"
2> 使用格式:指针名->属性名
#include
#include
#include
//声明一个英雄结构体类型
struct Hero
{
char name[20]; //姓名
int Hp; //血量
double speed; //基础位移
int kill; //人头数
}h3 = {"盖伦", 3500, 500, 5d}
struct //无名结构体
{
char name[40]; //名称
char position[40]; //产地
double price; //单价
double weight; //重量
}g1 = {"三鹿奶粉", "China", 350, 1000}; int main(int argc, const char *argv[]) }
//使用英雄类型定义一个英雄变量
struct Hero h1 = {"亚索", 650, 350, 0};
//此时定一个英雄变量h1 /
/定义英雄变量,指定某个成员进行赋值
struct Hero h2 = {.Hp=2000, .speed = 1000}; //输出英雄变量h1中的所有内容 printf("h1.name = %s\n", h1.name);
printf("h1.Hp = %d\n", h1.Hp);
printf("h1.speed = %lf\n", h1.speed);
printf("h1.kill = %d\n", h1.kill);
//在堆区申请一个英雄类型,完成初始化并输出相应的属性 s
truct Hero *ptr = (struct Hero*)malloc(sizeof(struct Hero));
//给英雄名字赋值 strcpy(ptr->name,"亚瑟");
//给姓名赋值 ptr->Hp = 3000;
//给hp属性赋值 ptr->speed = 350; ptr->kill = 3;
//输出英雄指针指向堆区空间中的内容
printf("英雄信息为:%s %d %.2lf %d\n", ptr->name, ptr->Hp, ptr->speed, ptr->kill); d