c语言笔记_4

1:返回指针和直接操作内存

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct node
{
    int data;

    struct node * next;;
}node_n;

node_n * init_node(node_n *head)
{
    head = (node_n *)malloc(sizeof(node_n));
    if(head == NULL)
    {
        perror("Failed!!!\n");
        return false;
    }

    head->data = 1000;

    head->next = NULL;

    return head;
}

bool init_node_1(node_n **head)
{
    (*head) = (node_n *)malloc(sizeof(node_n));
    if(head == NULL)
    {
        perror("Failed!!!\n");
        return NULL;
    }

    (*head)->data = 2000;

    (*head)->next = NULL;

    return true;
}

int main(void)
{
    node_n * head;

    head = init_node(head);
    printf("%d\n", head->data);
    init_node_1(&head);
    printf("%d\n", head->data);

    return 0;
}

在用函数对某一数据进行操作时,有这两种方式进行。一是直接去到对应内存进行修改,二是修改后将值返回,然后接收。

第一种方式就需要把指针的地址传给函数,所以是&head,类型是node_n **,是个二级指针,所以参数需要是node_n **,在操作时,(*head)才是传之前的head指针,所以在使用时需要用(*head)。

第二种方式就是直接传指针,这样修改函数内的head时,此时的head是形参,虽然这个形参也是指向的实参指向的地址,但在申请内存后,地址就改变了,这时候对形参的修改,就是在修改另一块内存,所以在修改完后,需要将修改后的值返回。而第一种方式的(*head)就是实参head,所以修改(*head)就是在修改实参head。

2:c的库函数

一些头文件,比如stdio.h,在linux系统中,在usr/include文件夹里,但这只是函数的声明,函数具体的实现在c库里。在The GNU C Library中,是各种函数的具体实现。

下载了The GNU C Library的源码后,(可于此处下载不同版本的.tar.gz文件),就可以在stdio-common中看到有关printf函数实现的各个文件,其实现十分复杂。

3:这样可以得到数组的大小。

#include <stdio.h>

int main(void)
{
    int arr[5];
    int len = sizeof(arr)/sizeof(int);
    printf("%d\n", len);

    return 0;
}

4:数的逆序操作

#include <stdio.h>

//将数的比特进行逆序,比如1011,逆序后是1101
unsigned int Reversed_Bits(unsigned int num) {
    unsigned int reversed = 0;//逆序后的值,初始为0
    for (int i = 0; i < 32; i++) {
        // 检查num的最低位是否为1
        //按位与操作,1&0=0,1&1=1
        //num的2-32位全部与成0,这样就可以判断第1位是1还是0
        if (num & 0x01) {
            // 如果是1,则将reversed对应的高位设置为1
            // 如果是0,就是跳过了,因为设置1时是直接把对应位设置为1
            reversed |= (1 << (31 - i));//对应位置1操作
            //reversed &=  ~(1 << (31 - i));//对应位置0操作
        }
        // 将num右移一位,检查下一位
        num >>= 1;//这个num是个形参,对它操作不影响什么
    }
    return reversed;//返回最终逆序得到的值
}

int main(void) {
    unsigned int num, rever_val;
    num = 1213;
    rever_val = Reversed_Bits(num);

    printf("rever_val is: %u (0x%X)\n", rever_val, rever_val);

    return 0;
}

5:extern关键字的作用

1.声明外部变量

extern用于声明某个变量或函数是在其他地方定义的,这样,就可以在文件a中使用文件b中的变量或者函数。这样就可以共享全局变量。

//file1.c
#include <stdio.h>

void my_function(void)
{
    printf("Hello World!");

    return;
}

//file2.c
#include <stdio.h>

extern void my_function(void);

int main(void)
{
    my_function();

    return 0;
}

//gcc .\file1.c .\file2.c -o extern
//.\extern.exe
//Hello World!

变量也是一个道理。

2.指定链接属性

c语言中,变量和函数有三种链接属性:内部(static),外部(默认),无链接(register)。extern可以明确指定其具有外部链接属性。

3.注意点

定义的变量只能是全局变量,如果定义在复合语句里,则找不到这个变量。

6:static关键字的作用

static用于声明一个变量或者函数具有静态链接属性,这意味着被static声明的变量或函数的作用域和声明周期都受到限制,这要分几种情况。

1.局部变量

用static声明局部变量时,变量的值会被保持,即使函数调用结束,也不会被销毁,正常来说,局部变量在函数调用结束后是会被销毁的。

//static_chara.c
#include <stdio.h>

// 函数内部声明一个static局部变量
void myFunction_1() {
    static int count = 0; // 声明一个static局部变量
    count++; // 每次调用函数时,count的值增加1
    printf("count is: %d\n", count);

    //函数调用结束后,一般来说,count这个变量就被销毁了,
    //但被static声明后,count会被保留,所以再次调用后,
    //会在原来的值的基础上进行++
}

int main() {
    // 多次调用myFunction函数,每次调用都会打印count的值
    myFunction_1();//1
    myFunction_1();//2
    myFunction_1();//3

    return 0;
}

// count is: 1
// count is: 2
// count is: 3

2.全局变量

下面有介绍。

7:linux内存分布

一个c程序运行后,不同的数据会存到不同的内存区域,如上图所示。

1.内核空间

内核空间是Linux系统内核代码所在处。

2.栈

栈的特点是向下增长,主要用来存命令行参数(如终端输入命令./program arg1 arg2 arg3,存的就是arg1 arg2 arg3),局部变量函数的参数值函数的返回地址栈空间的内存存储是随机值,也就是不确定的,也就是说,在C语言中,局部变量在栈上的默认初始值是不确定的,它们通常包含随机值,即栈空间中之前的遗留数据。因此,为了防止程序中出现不确定的行为,应该确保在使用局部变量之前对它们进行初始化。

栈空间大小默认8M,可修改。

3.动态链接库

当编译一个程序时使用动态库(动态链接库)时,该程序的可执行文件不包含这些库的代码,而是包含对库中函数的引用。在程序运行时,这些共享库的代码和数据会被动态地加载到进程的虚拟地址空间中的动态链接库区域。

特点:共享、只读、按需加载。

4.堆

使用malloc时,就是在申请堆区域的空间,栈是操作系统自动维护的,但堆不是,所以才需要手动释放申请的空间。

堆内存属于匿名内存,所以只能通过指针访问。

int *p = (int *)malloc(sizeof(int));

malloc是个函数,返回的是申请的内存的首地址,这个地址是void*类型,所以要想给p,就需要强制转换成int*类型。

有可能会申请失败,所以需要有错误处理。

if(p == NULL)
{
    perror("Memory apply failed!");
    retrun;
}

很明显,失败时malloc返回NULL。

5.全局数据区

5.1 .data段

.data段存放已经被初始化的全局变量和静态局部变量(注意:不是局部变量),并且初始化的值不能是0;

5.2 .bss段

.bss段存放没有被初始化的全局变量和静态局部变量,以及被初始化为0的全局变量和静态局部变量。

6.常量区

存放一般常量和字符常量,叫.rodata段。

7.程序代码区

也就是代码段,用于存放程序执行代码。分为两部分,.text和.init。

7.1 .text

.text存放程序生成的指令,包括函数的定义和程序执行时的控制流。控制流的那些指令是负责处理程序如何执行的,比如一般情况下程序是顺序执行的,但遇到循环、分支、跳转等情况时,就需要控制流来处理如何执行接下来的指令。

7.2 .init

在程序执行之前,需要进行一些初始化操作,进行这些初始化操作的指令就是存在.init中的。

8.保留区域

保留区也可以称为不可访问区域,用户是没有权限访问的,所以可以在初始化指针时把指针指向这个区域。宏定义NULL的值就是0X0000_0000,这样做就可以确保野指针的出现。

8.链接属性

函数和变量有三种链接属性:内部、外部、无链接。链接属性决定了变量的存储期和作用域,以及它如何在程序的不同部分中被访问。

默认是外部的,也就是说,文件a中声明一个全局变量,文件b中用extern就可访问到,因为都是指的同一个变量。

//link_attribure_1.c
#include <stdio.h>

extern int a;

int main(void)
{
    printf("The value of a is: %d\n", a);

    return 0;
}

//link_attribute_2.c
int a = 10;

//输出:The value of a is: 10

但如果把a的链接属性改为内部,也就是用static声明,文件1中就不可以访问到了。

//link_attribute_2.c
static int a = 10;


/*输出::link_attribute:(.rdata$.refptr.a[.refptr.a]+0x0): 
undefined reference to `a' collect2.exe: error: ld returned 
1 exit status
*/

无链接用register关键字声明,关键字 register 用于建议编译器将变量存储在CPU寄存器中,以提高访问速度。

9.指针函数和函数指针

指针函数是指返回指针的函数

int *max(int a, int b)
{
    //代码
}

函数指针是指指向函数的指针

void print(int a)
{
    printf("%d\n", a);
}

int main()
{
    void (*funcPtr)(int) = print;  // 函数指针funcPtr指向print函数
    funcPtr(10);  // 通过函数指针调用print函数
    return 0;
}

10.二级指针

声明一个指针,会有三个数据。

//pointer.c

#include <stdio.h>

int main(void)
{
    int *p;
    long b = (long int)p;
    int c = (*(int *)(b));

    printf("The value of p is: %p\nThe value of &p is: %p\nThe value of *p is: %d\n", p, &p, *p);
    printf("The value of c is: %d\n", c);

    return 0;
}

/*
The value of p is: 00000000000d151d
The value of &p is: 00000000005ffe80
The value of *p is: -1414812757
The value of c is: -1414812757
*/

如int *p,分别是p,&p,*p。p是指针指向的数据的地址,&p是指针的地址,*p是指针指向的数据的值。如下图所示:

所以可以根据输出结果看到,声明指针时没有初始化的话,指针指向的地址是不确定的,也就是数据地址是不确定的,其中的数据是多少,也就根据地址对应空间里存的是多少来定了。

可以让它指向一个确定的地址

//pointer.c

#include <stdio.h>

int main(void)
{
    int *p;
    int a = 111;
    p = &a;
    long b = (long int)p;
    int c = (*(int *)(b));

    printf("The value of p is: %p\nThe value of &p is: %p\nThe value of *p is: %d\n", p, &p, *p);
    printf("The value of c is: %d\n", c);

    return 0;
}

/*
The value of p is: 00000000005ffe7c
The value of &p is: 00000000005ffe80
The value of *p is: 111
The value of c is: 111
*/

当声明一个二级指针时,

//pointer.c

#include <stdio.h>

int main(void)
{
    int a = 101;
    int *p = &a;
    int *e = p;

    int **b = &p;

    printf("The value of p is: %p\n", p);
    printf("The value of &p is: %p\n", &p);
    printf("The value of *p is: %d\n", *p);

    printf("\nThe value of e is: %p\n", e);
    printf("The value of &e is: %p\n", &e);
    printf("The value of *e is: %d\n", *e);

    printf("\nThe value of b is: %p\n", b);
    printf("The value of &b is: %p\n", &b);
    printf("The value of *b is: %p\n", *b);
    printf("The value of **b is: %d\n", **b);

    return 0;
}

/*
The value of p is: 00000000005ffe8c
The value of &p is: 00000000005ffe80
The value of *p is: 101

The value of e is: 00000000005ffe8c
The value of &e is: 00000000005ffe78
The value of *e is: 101

The value of b is: 00000000005ffe80
The value of &b is: 00000000005ffe70
The value of *b is: 00000000005ffe8c
The value of **b is: 101
*/

**b叫做指向指针的指针,所以给它赋值时,需要给一个指针的地址。

注意哈,如果像指针e一样,这其实是在让指针e也指向数据a,跟int *e = &a;是没有区别的。

从结果中也可以看出,e的值就是数据a的地址。

**b就有四个数据,&b、b、*b、**b。

&b是指针b的地址,自然是个系统分配的值。

*b是个指针,**b自然就叫做指向指针的指针,所以b就应该是一个指针的地址,这里是&p。

那么*b,就相当于得到&p这个地址对应内存空间中的内容,也就是p。

而&p对应的内容还是个地址,是数据的地址,所以进一步解地址,**b就是数据的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值