数据结构Day1:

1:值传递和地址传递

左值:

左值是可标识且可寻址的表达式,它可以出现在赋值操作符的左边。换句话说,左值表示一个具体的内存位置。例如,变量、数组元素和对象成员都可以是左值。

右值:

右值是指不能寻址的表达式,它只能出现在赋值操作符的右边。右值可以是字面量、临时对象和表达式的结果。右值是临时创建的,它们没有固定的内存位置。

区别:

左值表示可标识且可寻址的表达式,而右值表示不能寻址的临时表达式。

值传递:

值传递是一种参数传递的方式,其中将参数的值复制给函数的形式参数。在值传递中,原始参数的值在函数调用期间被复制到新的内存位置,而函数中使用的是这个副本。

当函数使用值传递方式调用时,对形参的修改不会影响到原始参数。这是因为函数内使用的是参数的副本,而不是原始参数本身。这也意味着在函数内部对参数的修改在函数外部是不可见的。

值传递的主要优点是简单,容易理解和使用。它不会影响到原始参数,因此可以避免意外的副作用和数据污染。此外,由于使用的是参数的副本,函数的调用方和被调用方之间相互独立,不会相互干扰。

然而,值传递也有一些缺点。首先,复制参数的值可能会消耗一定的时间和内存。如果参数是较大的对象或数据结构,则复制的开销可能会很大。其次,对于需要在函数中修改原始参数的情况,值传递无法实现这一目标,因为函数只能修改副本。

总之,值传递是一种简单和安全的参数传递方式,适用于不需要修改原始参数且参数较小的情况。但对于大对象或需要修改原始参数的场景,其他参数传递方式(如引用传递或指针传递)可能更合适。

地址传递:

地址传递是一种参数传递的方式,其中函数接收参数的内存地址而不是参数的值本身。通过地址传递,函数可以直接访问和修改原始参数所在的内存位置。

在地址传递中,函数的形式参数是指针类型,它存储了原始参数的内存地址。通过解引用操作符(*)或指针操作符(->),函数可以访问和修改原始参数的值。

使用地址传递的主要优点是可以实现对原始参数的修改。由于函数直接操作原始参数所在的内存位置,所做的修改在函数外部是可见的。这种特性对于需要修改原始参数的情况(如交换两个值、修改数组等)非常有用。

此外,地址传递避免了复制参数的值所带来的开销,特别是对于大型对象或数据结构。通过传递地址,函数可以直接访问原始参数的内容,而不需要进行复制。

然而,地址传递也有一些注意事项。首先,需要确保在函数中不会意外地修改原始参数的值,以避免引入潜在的错误和副作用。其次,使用地址传递可能增加代码的复杂性和难以理解性。

总结来说,地址传递是一种可以实现对原始参数修改的参数传递方式,适用于需要修改原始参数的情况以及对参数的复制开销敏感的场景。但需要注意在函数内部正确处理和保护原始参数的值。

值传递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;
}
 

 2:内存分区

数据结构Day1:_第1张图片

 1:系统分配4G虚拟内存的空间

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段

3:动态内存分配和回收(melloc & free)

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;
}

 

4.类型重定义

类型重定义本质上是给类型重新起个名字,使得代码更加易于理解,例如:将unsigned long int 改名为uint64,将unsigned short int 改名为uint16

1: 使用格式

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; //定义二级指针变量

2> 提取类型

int ; //定义普通数据类型

int *; //定义指针类型

int [5]; //定义数组类型

int *[5]; //定义指针数组

int (*)[5]; //定义数组指针

int (*)(int,int); //定义函数指针

int (*[3])(int, int); //定义函数指针数组的指针

int **; //定义二级指针

3> 给类型重新起名

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
#include

typedef 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);               //520

    Ptr_i p1 = NULL;            //此时p1是指针变量  int *p1;
    printf("sizeof p1 = %ld\n", sizeof(p1));        //8

    String 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;
}
 

 类型重定义与宏定义的区别
1>    宏定义只是单纯的替换,不做任何正确性检测,是一个预处理指令
2>    类型重定义,需要做正确性检测,是一条语句
3>    宏替换发生在预处理阶段,而类型重定义发生在编译阶段
4>    如果是对普通单个重命名没有问题,但是对指针重命名就有问题了

#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; }

 

结构体

1 引入目的

系统提供的数据类型不够用了,没有条件创造条件,自己定义数据类型,自己用

2 定义

有相同数据类型和不同数据类型构成的集合叫结构体,属于构造数据类型

3 定义格式

 

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;            //人头数
};
 

 结构体变量的定义及初始化
1>    定义结构体变量格式:struct  结构体类型名  变量名1,变量名2;
2>    也可以在声明结构体类型时,顺便定义结构体变量(二合一)
3>    定义结构体变量初始化时,可以指定某个属性进行初始化工作,没有初始化的内容全部时随机值
4>    可以使用无名结构体,定义结构体类型时,需要定义结构体变量,后期该结构体就不能定义变量了

 

 #include //声明一个英雄结构体类型

struct Hero

{

char name[20]; //姓名

int Hp; //血量

double speed; //基础位移 i

nt kill; //人头数

}h3 = {"盖伦", 3500, 500, 5};

 练习:定义一个商品类型,成员属性:商品名称(name)、产地(position)、单价(price)、重量(weight)

 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}

 练习:定义一个商品类型,成员属性:商品名称(name)、产地(position)、单价(price)、重量(weight)

 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

 

思维导图 

 

 

你可能感兴趣的:(数据结构)