C语言进阶(一)—— 内存分区:变量和内存分布

news2025/2/28 3:47:24

1. 数据类型

1.1 数据类型概念

什么是数据类型?为什么需要数据类型?
数据类型是为了更好进行内存的管理,让编译器能确定分配多少内存。
  • 类型是对数据的抽象;

  • 类型相同的数据具有相同的表示形式、存储格式以及相关操作;

  • 程序中所有的数据都必定属于某种数据类型;

  • 数据类型可以理解为创建变量的模具: 固定大小内存的别名;

1.2 数据类型别名

typedef使用

//#define _CRT_SECURE_NO_WARNINGS  //VS不建议使用传统库函数,如果不用这个宏,会出现一个错,编号:C4996
#include<stdio.h>   // std 标准 i input  输入   o  output 输出 
#include<string.h>  // strcpy   strcmp  strcat  strstr
#include<stdlib.h>  // malloc  free


//1、typedef使用  简化结构体关键字 struct
//struct Person
//{
//    char name[64];
//    int age;
//};
//typedef struct Person  myPerson;

//主要用途  给类型起别名
//语法  typedef  原名  别名
typedef struct Person
{
    char name[64];
    int age;
}myPerson;

void test01()
{
    struct Person p1 = { "张三", 19 };

    myPerson p2 = { "李四", 20 };
}


// 2、区分数据类型
void test02()
{
    //char * p1, p2;  //p1是char *  而 p2 是char

    typedef char * PCHAR;
    PCHAR p1, p2;

    char *p3, *p4; // p3 和 p4都是char *
}


//3、提高代码移植性
typedef int MYINT; //typedef long long MYINT; 只需要替换 long long 就可以了
void test03()
{
    MYINT a = 10;

    MYINT a2 = 10;
}


//程序入口
int main1(){
    test01();

    system("pause"); // 按任意键暂停  阻塞功能

    return EXIT_SUCCESS; //返回 正常退出值  0
}

1.3 void数据类型

void字面意思是”无类型”,void* 无类型指针,无类型指针可以指向任何类型的数据。

void定义变量是没有任何意义的,当你定义void a,编译器会报错。

void真正用在以下两个方面:

  • 函数返回的限定;

  • 函数参数的限定;

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

//1、无类型是不可以创建变量的
void test01()
{
    //void a = 10; //编译器直接报错,因为不知道给a分配多少内存空间
}


//2、可以限定函数返回值
void func()
{
    //return 10;
}

void test02()
{
    //func();
    //printf("%d\n", func());
}


//3、限定函数参数列表
int func2(void)
{
    return 10;
}
void test03()
{
    //printf("%d\n", func2(10));
}


//4、void *  万能指针
void test04()
{
    void * p = NULL;

    int  * pInt = NULL;
    char * pChar = NULL;

    //pChar = (char *)pInt;
    pChar = p; //万能指针  可以不需要强制类型转换就可以给等号左边赋值

    printf("size of void *   = %d\n", sizeof(p));

}

int main(){
    //test02();
    //test03();
    test04();
    system("pause");
    return EXIT_SUCCESS;
}

1.4 sizeof操作符

sizeof是c语言中的一个操作符,类似于++、--等等。sizeof能够告诉我们编译器为某一特定数据或者某一个类型的数据在内存中分配空间时分配的大小,大小以字节为单位

基本语法:
sizeof(变量);
sizeof 变量;
sizeof(类型);

sizeof 注意点

  • sizeof返回的占用空间大小是为这个变量开辟的大小,而不只是它用到的空间。和现今住房的建筑面积和实用面积的概念差不多。所以对结构体用的时候,大多情况下就得考虑字节对齐的问题了;

  • sizeof返回的数据结果类型是unsigned int

  • 要注意数组名和指针变量的区别。通常情况下,我们总觉得数组名和指针变量差不多,但是在用sizeof的时候差别很大,对数组名用sizeof返回的是整个数组的大小,而对指针变量进行操作的时候返回的则是指针变量本身所占得空间,在32位机的条件下一般都是4。而且当数组名作为函数参数时,在函数内部,形参也就是个指针,所以不再返回数组的大小;

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

//1、sizeof本质, 是不是一个函数??? 不是函数,只是一个操作符,类似+-*/
void test01()
{
    //对于数据类型 ,sizeof必须用()去使用,但是对于变量,可以不加()
    printf("size of int = %d\n", sizeof(int));

    double d = 3.14;

    printf("size of d = %d\n", sizeof d );

}

//2、sizeof的返回值类型是什么 ?  unsigned int 无符号整型
void test02()
{
    //unsigned int a = 10;
    //if (a - 20 > 0) //当unsigned int和int类型数据做运算,编译器会将数据类型都转为unsigned int
    //{
    //    printf("大于 0 \n");
    //}
    //else
    //{
    //    printf("小于 0 \n");
    //}


    if ( sizeof(int) - 5 > 0 )
    {
        printf("大于 0 %u \n", sizeof(int)-5);
    }
    else
    {
        printf("小于 0 \n");
    }

}

//3、sizeof可以统计数组长度
//当数组名作为函数参数时候,会退化为指针,指向数组中第一个元素
void calculateArray( int arr[] )
{
    printf("arr的数组长度: %d\n", sizeof(arr));
}

void test03()
{
    int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8 };

    //printf("arr的数组长度: %d\n", sizeof(arr));

    calculateArray(arr);
}

int main(){
    //test01();
    //test02();
    test03();

    system("pause");
    return EXIT_SUCCESS;
}

1.5 数据类型总结

  • 数据类型本质是固定内存大小的别名,是个模具,C语言规定:通过数据类型定义变量

  • 数据类型大小计算(sizeof);

  • 可以给已存在的数据类型起别名typedef

  • 数据类型的封装(void 万能类型);

2. 变量

1.1 变量的概念

既能读又能写的内存对象,称为变量;

若一旦初始化后不能修改的对象则称为常量。

变量定义形式: 类型 标识符, 标识符, … , 标识符

1.2 变量名的本质

  • 变量名的本质:一段连续内存空间的别名

  • 程序通过变量来申请和命名内存空间int a = 0;

  • 通过变量名访问内存空间;

  • 不是向变量名读写数据,而是向变量所代表的内存空间中读写数据;

修改变量的两种方式:直接、间接

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

void test01()
{
    int a = 10;
    //直接修改
    a = 20;
    printf("a = %d\n", a);
    //间接修改
    int * p = &a;
    *p = 100;

    printf("a = %d\n", a);

}

//对于自定义数据类型
struct Person
{
    char a; // 0 ~ 3
    int b;  // 4 ~ 7
    char c; // 8 ~ 11
    int d;  // 12 ~ 15
};
void test02()
{
    struct Person p1 = { 'a', 10, 'b', 20 };

    //直接修改  d 属性
    p1.d = 1000;

    //间接修改  d 属性
    struct Person * p = &p1;
//    p->d = 2000;

    //printf("%d\n", p);
    //printf("%d\n", p+1);

    char * pPerson = p;

    printf("d = %d\n", *(int*)(pPerson + 12));
    printf("d = %d\n",  *(int*)((int*)pPerson +3) );
}

int main(){

    //test01();
    test02();
    system("pause");
    return EXIT_SUCCESS;
}

3. 程序的内存分区模型

3.1 内存分区

3.1.1 运行之前

我们要想执行我们编写的c程序,那么第一步需要对这个程序进行编译。

预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法
编译:检查语法,将预处理后文件编译生成汇编文件
汇编:将汇编文件生成目标文件(二进制文件)
链接:将目标文件链接为可执行程序

当我们编译完成生成可执行文件之后,我们通过在linux下size命令可以查看一个可执行二进制文件基本情况:

通过上图可以得知,在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、数据区(data)和未初始化数据区(bss)3个部分(有些人直接把data和bss合起来叫做静态区或全局区)。

代码区
存放 CPU 执行的机器指令。通常代码区是可 共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是 只读的,使其只读的原因是防止程序意外地修改了它的指t令。另外,代码区还规划了局部变量的相关信息。

全局初始化数据区/静态数据区(data段)
该区包含了在程序中明确被 初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。

未初始化数据区(又叫 bss 区)
存入的是全局 未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为 0 或者空(NULL)。

总体来讲说,程序源代码被编译之后主要分成两种段:程序指令(代码区)和程序数据(数据区)。代码段属于程序指令,而数据域段和.bss段属于程序数据。

那为什么把程序的指令和程序数据分开呢?

  • 程序被load到内存中之后,可以将数据和代码分别映射到两个内存区域。由于数据区域对进程来说是可读可写的,而指令区域对程序来讲说是只读的,所以分区之后呢,可以将程序指令区域和数据区域分别设置成可读可写或只读。这样可以防止程序的指令有意或者无意被修改;

  • 当系统中运行着多个同样的程序的时候,这些程序执行的指令都是一样的,所以只需要内存中保存一份程序的指令就可以了,只是每一个程序运行中数据不一样而已,这样可以节省大量的内存。比如说之前的WindowsInternet Explorer 7.0运行起来之后, 它需要占用112 844KB的内存,它的私有部分数据有大概15 944KB,也就是说有96900KB空间是共享的,如果程序中运行了几百个这样的进程,可以想象共享的方法可以节省大量的内存

3.1.1 运行之后

程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。然后,运行可执行程序,操作系统把物理硬盘程序load(加载)到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区、堆区

代码区(text segment)
加载的是可执行文件代码段,所有的可执行代码都加载到代码区,这块内存是不可以在运行期间修改的。

未初始化数据区(BSS)
加载的是可执行文件BSS段,位置可以分开亦可以紧靠数据段,存储于数据段的数据(全局未初始化,静态未初始化数据)的生存周期为整个程序运行过程。
全局初始化数据区/静态数据区(data segment)
加载的是可执行文件数据段,存储于数据段(全局初始化,静态初始化数据,文字常量(只读))的数据的生存周期为整个程序运行过程。

栈区(stack)
栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。

堆区(heap)
堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

类型

作用域

生命周期

存储位置

auto变量

一对{}内

当前函数

栈区

static局部变量

一对{}内

整个程序运行期

初始化在data段,未初始化在BSS段

extern变量

整个程序

整个程序运行期

初始化在data段,未初始化在BSS段

static全局变量

当前文件

整个程序运行期

初始化在data段,未初始化在BSS段

extern函数

整个程序

整个程序运行期

代码区

static函数

当前文件

整个程序运行期

代码区

register变量

一对{}内

当前函数

运行时存储在CPU寄存器

字符串常量

当前文件

整个程序运行期

data段

注意:建立正确程序运行内存布局图是学好C的关键!!

3.2 分区模型

3.2.1 栈区

由系统进行内存的管理。主要存放函数的参数以及局部变量。在函数完成执行,系统自行释放栈区内存,不需要用户管理。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

//栈 注意事项 ,不要返回局部变量的地址
int * func()
{
    int a = 10;
    return &a;
}

void test01()
{
    int * p = func();

    //结果已经不重要了,因为a的内存已经被释放了,我们没有权限去操作这块内存
    printf("a = %d\n", *p);
    printf("a = %d\n", *p);
}


char * getString()
{
    char str[] = "hello world";
    return str;
}

void test02()
{
    char * p = NULL;
   //不要返回局部变量的地址,因为局部变量在函数执行之后就释放了,我们没有权限取操作释放后的内存
    p = getString();
    printf("%s\n", p); //乱码,每次不一样
}

int main(){

    //test01();
    test02();
    system("pause");
    return EXIT_SUCCESS;
}

3.2.2 堆区

由编程人员手动申请,手动释放,若不手动释放,程序结束后由系统回收,生命周期是整个程序运行期间。使用malloc或者new进行堆的申请。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

int * getSpace()
{
    int * p = malloc(sizeof(int)* 5);

    if (p == NULL)
    {
        return NULL;
    }

    for (int i = 0; i < 5;i++)
    {
        p[i] = i + 100;
    }
    return p;

}

void test01()
{
    int * p = getSpace();

    for (int i = 0; i < 5;i++)
    {
        printf("%d\n", p[i]);
    }

    //手动在堆区创建的数据,记得手动释放
    free(p);
    p = NULL;

}


//注意事项
//如果主调函数中没有给指针分配内存,被调函数用同级指针是修饰不到主调函数中的指针的
void allocateSpace( char * pp )
{
    char * temp =  malloc(100);
    if (temp == NULL)
    {
        return;
    }
    memset(temp, 0, 100);
    strcpy(temp, "hello world");
    pp = temp;

}

void test02()
{
    char * p = NULL;
    allocateSpace(p);

    printf("%s\n", p);
}


void allocateSpace2(char ** pp)
{
    char * temp = malloc(100);
    memset(temp, 0, 100);
    strcpy(temp, "hello world");
    *pp = temp;
}

void test03()
{
    char * p = NULL;

    allocateSpace2(&p);

    printf("%s\n", p);

    free(p);
    p = NULL;

}



int main(){
    //test01();
    //test02();
    test03();
    system("pause");
    return EXIT_SUCCESS;
}
#include <stdlib.h>
void *calloc(size_t nmemb, size_t size);
功能:
在内存动态存储区中分配nmemb块长度为size字节的连续区域。calloc自动将分配的内存 置0。
参数:
nmemb:所需内存单元数量
size:每个内存单元的大小(单位:字节)
返回值:
成功:分配空间的起始地址
失败:NULL
#include <stdlib.h>
void *realloc(void *ptr, size_t size);
功能:
重新分配用malloc或者calloc函数在堆中分配内存空间的大小。
realloc不会自动清理增加的内存,需要手动清理,如果指定的地址后面有连续的空间,那么就会在已有地址基础上增加内存,如果指定的地址后面没有空间,那么realloc会重新分配新的连续内存,把旧内存的值拷贝到新内存,同时释放旧内存。
参数:
ptr:为之前用malloc或者calloc分配的内存地址,如果此参数等于NULL,那么和realloc与malloc功能一致
size:为重新分配内存的大小, 单位:字节
返回值:
成功:新分配的堆内存地址
失败:NULL

3.2.3 全局/静态区

全局静态区内的变量在编译阶段已经分配好内存空间并初始化。这块内存在程序运行期间一直存在,它主要存储全局变量静态变量常量

注意:

(1)这里不区分初始化和未初始化的数据区,是因为静态存储区内的变量若不显示初始化,则编译器会自动以默认的方式进行初始化,即静态存储区内不存在未初始化的变量。

(2)全局静态存储区内的常量分为常变量和字符串常量,一经初始化,不可修改。静态存储内的常变量是全局变量,与局部常变量不同,区别在于局部常变量存放于栈,实际可间接通过指针或者引用进行修改,而全局常变量存放于静态常量区则不可以间接修改。

(3)字符串常量存储在全局/静态存储区的常量区。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

int v1 = 10;//全局/静态区
const int v2 = 20; //常量,一旦初始化,不可修改
static int v3 = 20; //全局/静态区
char *p1; //全局/静态区,编译器默认初始化为NULL

//那么全局static int 和 全局int变量有什么区别?

void test(){
    static int v4 = 20; //全局/静态区
}

//1、静态变量
static int a = 10; //特点:只初始化一次,在编译阶段就分配内存,属于内部链接属性,只能在当前文件中使用

void test01()
{
    static int b = 20; //局部静态变量,作用域只能在当前test01中

    //a 和 b的生命周期是一样的
}

//2、全局变量

extern int g_a = 100; //在C语言下 全局变量前都隐藏加了关键字  extern,属于外部链接属性

void test02()
{
    extern int g_b;//告诉编译器 g_b是外部链接属性变量,下面在使用这个变量时候不要报错

    printf("g_b = %d\n", g_b);

}

int main(){
    //test();
    //test01();
    test02();

    system("pause");
    return EXIT_SUCCESS;
}
static 和 extern 区别
static 静态变量:编译阶段分配内存,只能在当前文件内使用,只初始化一次
extern 全局变量:C语言下默认的全局变量前都隐藏的加了该关键字

const修饰的变量
全局变量:直接修改失败;间接修改失败 原因放在常量区,受到保护
局部变量:直接修改失败;间接修改成功 原因放在栈上;伪常量不可以初始化数组

字符串常量是否可修改?字符串常量优化:
不同的编译器可能有不同的处理方式
ANSI没有指定出标准
有些编译器可以修改字符串常量,有些不可以
有些编译器将相同的字符串常量看成同一个

字符串常量地址是否相同?
tc2.0,同文件字符串常量地址不同。
Vs2013,字符串常量地址 同文件和不同文件都相同
Devc++、QT同文件相同,不同文件不同。

3.2.4 总结

在理解C/C++内存分区时,常会碰到如下术语:数据区静态区常量区全局区字符串常量区文字常量区代码区等等,初学者被搞得云里雾里。在这里,尝试捋清楚以上分区的关系。

数据区包括:堆,栈,全局/静态存储区。

全局/静态存储区包括:常量区,全局区、静态区。

常量区包括:字符串常量区、常变量区。

代码区:存放程序编译后的二进制代码,不可寻址区。

可以说,C/C++内存分区其实只有两个,即代码区和数据区

3.3 函数调用流程

3.3.1 宏函数

#define MYADD( x, y) ((x) + (y)) //注意:保证运算完整性
在一定程度上会比普通函数效率高,普通函数会有入栈和出栈的时间开销
将比较频繁短小的函数 写为宏函数,直接跑源码
优点:以空间换时间

3.3.2 调用惯例

主调函数和被调函数都必须有一致的约定,才可以正确的调用函数,这个约定我们称为调用惯例;
调用惯例包含的内容:出栈方、参数的传入顺序、函数名称的修饰;
c和c++下默认的调用惯例为 cdecl
注意: _cdecl不是标准的关键字,在不同的编译器里可能有不同的写法,例如gcc里就不存在_cdecl这样的关键字,而是使用__attribute__((cdecl)).

调用惯例

出栈方

参数传递

名字修饰

cdecl

函数调用方

从右至左参数入栈

下划线+函数名

stdcall

函数本身

从右至左参数入栈

下划线+函数名+@+参数字节数

fastcall

函数本身

前两个参数由寄存器传递,其余参数通过堆栈传递。

@+函数名+@+参数的字节数

pascal

函数本身

从左至右参数入栈

较为复杂,参见相关文档

3.4 栈的生长方向和内存存放方向

栈底 --- 高地址

栈顶 --- 低地址

高位字节数据 --- 高地址

低位字节数据 --- 低地址

小端对齐

//1. 栈的生长方向
void test01(){

    int a = 10;
    int b = 20;
    int c = 30;
    int d = 40;

    printf("a = %d\n", &a);
    printf("b = %d\n", &b);
    printf("c = %d\n", &c);
    printf("d = %d\n", &d);

    //a的地址大于b的地址,故而生长方向向下
}

//2. 内存生长方向(小端模式)
void test02(){
    
    //高位字节 -> 地位字节
    int num = 0xaabbccdd;
    unsigned char* p = &num;

    //从首地址开始的第一个字节
    printf("%x\n",*p);
    printf("%x\n", *(p + 1));
    printf("%x\n", *(p + 2));
    printf("%x\n", *(p + 3));
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/347190.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

注解ConfigurationProperties、EnableConfigurationProperties的用法

1 ConfigurationProperties ConfigurationProperties主要作用就是将prefix属性指定的前缀配置项的值绑定到这个JavaBean上 &#xff0c;通过指定的前缀&#xff0c;来绑定配置文件中的配置。这样的好处是将配置数据与JOPO进行转换&#xff0c;能够管理一个类别的所有配置信息&…

【零基础入门前端系列】—表单(七)

【零基础入门前端系列】—表单&#xff08;七&#xff09; 一、什么是表单 表单在Web网页中用来给访问者填写信息&#xff0c;从而采集客户信息端&#xff0c;使得网页具有交互功能。一般是将表单设计在一个HTML文档中&#xff0c;当用户填写完信息后做提交操作&#xff0c;于…

docker部署springboot项目

1、创建放置项目jar包和Dockerfile的文件夹 cd usr/ mkdir reggie cd reggie/ 2、上传Dockerfile和项目jar包 Dockerfile内容如下&#xff1a; # 基础镜像使用java FROM java:8 # 作者 MAINTAINER chenxiansheng # VOLUME 指定了临时文件目录为/tmp。 # 其效果是在主机 /v…

ElasticSearch与Kibana入门(解决报错:can not run elasticsearch as root)

ElasticSearch安装和部署 es官网 window版 选择你要的版本 解压&#xff0c;bin下bat就可以启动&#xff0c;需要配置jdk18的环境 linux版 解压&#xff1a;tar zxvf elasticsearch-6.3.2.tar.gz 启动&#xff1a;bin下执行/elasticsearch 报错org.elasticsearch.boot…

C语言初级指针应用

目录 一.什么是指针以及如何获取地址 二.间接运算符&#xff08;解引用运算符&#xff09;&#xff1a;* 三.声明指针 四.使用指针在函数进行通信 五.变量&#xff1a;名称&#xff0c;地址和值 一.什么是指针以及如何获取地址 指针是一个值为内存地址的变量&#xff08;或…

RabbitMQ相关概念介绍

这篇文章主要介绍RabbitMQ中几个重要的概念&#xff0c;对于初学者来说&#xff0c;概念性的东西可能比较难以理解&#xff0c;但是对于理解和使用RabbitMQ却必不可少&#xff0c;初学阶段&#xff0c;现在脑海里留有印象&#xff0c;随着后续更加深入的学习&#xff0c;就会很…

电源自动测试系统-电源模块批量自动化测试方案ATECLOUD-Power

1、测试名称 基于ATECLOUD的电源模块测试方案 2、测试目的 提升电源模块测试效率&#xff0c;减少测试人员成本&#xff0c;降低测试专业知识要求&#xff0c;增加数据精准度&#xff0c;为企业提供专业决策的数据支持&#xff0c;从而降本增效。 3、测试设备 示波器、电子…

20230215小结

1 t-sne 原理&#xff1a;利用两个向量之间的欧式距离转化成条件概率分布&#xff0c;可以把高维度的数据转化为低维度&#xff08;1000&#xff0c;64&#xff09;-》&#xff08;1000&#xff0c;2&#xff09;&#xff0c;原先每个样本有64维度&#xff0c;转化为2维 2 swi…

linux系统编程入门

一、搭建环境 1、安装 Linux 系统&#xff08;虚拟机安装、云服务器&#xff09; https://releases.ubuntu.com/bionic/ 2、安装 XSHELL、XFTP https://www.netsarang.com/zh/free-for-home-school/ 3、安装 visual studio code https://code.visualstudio.com/ 4、Linu…

Unreal Engine角色涌现行为开发教程

在本文中&#xff0c;我将讨论如何使用虚幻引擎、强化学习和免费的机器学习插件 MindMaker 在 AI 角色中生成涌现行为。 目的是感兴趣的读者可以使用它作为在他们自己的游戏项目或具体的 AI 角色中创建涌现行为的指南。 推荐&#xff1a;使用 NSDT场景设计器 快速搭建 3D场景。…

一种基于加密域的数字图像水印算法的设计与实现(附Matlab源码)

一种基于加密域的数字图像水印算法的设计与实现 项目介绍 毕设项目 题目&#xff1a;一种基于加密域的数字图像水印算法的设计与实现 随着数字媒体技术的发展&#xff0c;数字媒体版权的保护得到了越来越多人的重视&#xff0c;数字水印技术作为数字媒体版权保护的有效手段…

通达信交易接口以什么形式执行下单的?

通达信程交易接口 以API形式来执行下单接口&#xff0c;一般不再需要通过接口系统之间进行连接&#xff0c;通过直接调用通达信dll交易函数的方式直接进行交易&#xff0c;包括下单&#xff0c;撤单&#xff0c;查询资金股份、当日委托、当日成交等方面都能很快的执行出来。以a…

【JDK8】MyBatis源码导入Idea

1.背景 为了更好的将MyBatis的开发设计思想带到日常开发工作&#xff0c;将MyBatis源码导入到本地开发工具中(idea)。我自己在导入的时候碰到几个问题&#xff0c;耽误了自己一点时间&#xff0c;这里我把它们记下来&#xff0c;后边的小伙伴可不要踩我的坑。 Java版本&#x…

黑帽SEO是什么?做了真的能够一直保持排名?

随着Google演算法一次又一次的更新&#xff0c;现在愈来愈多人重视所谓的网站SEO。但是内行的人都知道&#xff0c;网站要提高排名并非一天两天的事&#xff0c;所以有些人就会使用不法手段想借此提高排名&#xff0c;这也就是常听到的「黑帽SEO」。但是做黑帽SEO真的能快速提高…

【爬虫+数据清洗+可视化分析】用Python分析哔哩哔哩“狂飙”的评论数据

一、背景介绍您好&#xff0c;我是马哥python说&#xff0c;一枚10年程序猿。2023开年这段时间&#xff0c;《狂飙》这部热播剧引发全民追剧&#xff0c;不仅全员演技在线&#xff0c;更是符合反黑主旋律&#xff0c;因此创下多个收视率记录&#xff01;基于此热门事件&#xf…

嵌入式开发之Vscode实用插件大全

嵌入式开发之Vscode实用插件大全① Chinese (Simplified) (简体中文) &#xff08;神器&#xff09;② C/C &CMake & C/C Extension Pack&#xff08;神器&#xff09;③ Better C Syntax④ Doxygen Documentation Generator&#xff08;神器&#xff09;⑤ vscode-ico…

存储类别、链接与内存管理(一)

1、一些必要的基础概念 &#xff08;1&#xff09;对象 从硬件的角度&#xff0c;被存储的每个值都被占用了一定的物理内存&#xff0c;C语言把这样的一块内存称为对象对象可以存储一个或多个值一个对象可能并未存储实际的值&#xff0c;也可能存储一个或多个值&#xff0c;但…

初阶函数递归经典例题(1)

1、递归实现n的k次方 2、计算一个数的每位之和&#xff08;递归实现&#xff09; 3、strlen的模拟&#xff08;递归实现&#xff09; 讲解之间我们先回顾下递归的知识点&#xff1a; 1、什么是递归&#xff1f; 程序调用自身的编程技巧称为递归。&#xff08;即一个函数在其…

蓝牙耳机什么牌子的好又实惠?实惠好用的蓝牙耳机品牌

随着科技的发展&#xff0c;耳机领域的新品是越来越多&#xff0c;很多品牌如雨后春笋般涌现&#xff0c;耳机的样式也是层出不穷&#xff0c;下面小编整理了几款实惠好用的蓝牙耳机品牌。 一、南卡小音舱蓝牙耳机 参考价格&#xff1a;239元 单耳重&#xff1a;3.1g 推荐系…

用ChatGPT写一个基于ChatGPT API的对话机器人

采用的是国区的网站 Q&#xff1a;写一个调用chatgpt的聊天机器人的python程序 A&#xff1a; python import requests# 聊天机器人的API地址 url https://api.chatgpt.com/v2/query# 请求参数 params {prompt: 你好,user_key: YOUR_USER_KEY }# 发送请求 response request…