跳转至

1 Memory Model

文本统计:约 1679 个字 • 110 行代码

1.1 变量和动态内存分配

C++中的变量类型

(1)global variable 全局变量,存储在全局变量区,可以在不同的cpp文件之间共享,可以使用关键字extern 来使用别的cpp文件中的全局变量

Note

在 1.cpp 中定义全局变量 int a, 在 2.cpp 中使用 extern int a 调用全局变量

(2)static global variable 静态全局变量,不能在cpp文件之间共享

(3)local variable 存储在区上

(4)static local variable 静态局部变量

  • 存储在全局变量区
  • 在初次使用的时候初始化,keeps its value between visit to the function

(5)allocated variable 动态分配的变量 - 存储在内存的堆结构中

Where are they in memory?

stack

  • local vars

heap

  • dynamically allocated vars.

code/data

  • global vars

  • static global vars

  • static local vars

C++动态内存分配

new用于动态分配内存给变量,如new intnew double[1000]

  • 与malloc的区别:malloc不执行类的构造函数,而new出新的对象的时候会执行对象的构造函数new Class_Name[x]会执行x次Class_Name的构造函数

内存泄漏 Memory Leak

int *p = new int;
*p = 123;
p = new int;

上面这段代码中一开始为指针p分配了一段内存空间并赋值了123,但是第三行代码又为p赋值了一段新的内存空间,原来的内存空间存储了123,但是这一段内存空间已经没有指针指向,因此不能访问,也不能删除,造成了内存泄漏

delete用于删除动态分配的内存

  • 用法 delete p; delete[] p;
  • 和new类似,delete会执行所删除对象的构造函数,不能delete没有定义过的变量,同一个变量不能delete两次

同一个变量不能delete两次

两个指针p1,p2指向同一个数据,如果p1被delete了,p2也不能访问原本p1指向的变量的值,因为delete删除的是内存里的数据

int *p1 = new int;
int *p2 = p1;
*p2 = 1;
delete p1;
cout<<*p2<<endl; // error!!!

指向同一个数据的两个指针实际上只是两个不同的变量名而已,delete删除的不是变量名而是数据

1.2 引用 Reference

a new type in C++, 相当于给变量取了一个别名。

Defining references

定义引用的方式有两种,对于一般的引用如下定义即可,

type& refname = name;

另一种就是在函数参数列表种或者构造函数中定义即可。

基本的用法如下,就相当于给 X 取了一个别名Y

int X = 47;
// Y is a reference to X, X and Y now refer to
// the same variable
int &Y = X; 
cout << "Y = " << Y; // prints Y = 47
Y = 18;
cout << "X = " << X; // prints X = 18

Rules of references

(1)不能定义空引用,引用必须连接到一块合法的内存,下面展示了两种定义方式

int x = 3;
int& y = x;
const int& z = x;

void f (int& x);
f(y); // initialized when function is called

(2)不像指针一样可以重绑

int& y = x;
y = z; // Copy the value of z to y (a.k.a. x)

(3)The target of a non-const reference must be an lvalue. 非常值引用不可以是左值

void func (int &);
func (i * 3); // Warning or Error!

Type restrictions

No references to references

No pointers to references, but reference to pointer is ok

int&* p; // illegal
void f(int*& p); // ok

No arrays of references

引用和指针的区别

1.3 const 类型

用于定义常量类型,如const int x=12345;, const类型的变量在初始化之后就不能改变其值,const型变量不能在连接单元外使用

const int x = 123;
x = 27; // illegal!
x++; // illegal!

int y = x; // ok, copy const to non-const
y = x; // ok, same thing
const int z = y; // ok, const is safer

在C++中,如果你在一个文件中定义了一个const变量而不使用extern关键字,那么这个const变量默认具有内部链接。这意味着该const变量只能在定义它的文件内访问,其他文件无法访问它,即使你尝试通过extern声明来引用它。这种行为有助于避免不同编译单元之间的命名冲突。然而,如果你希望一个const变量能够在多个源文件之间共享,你可以使用extern关键字显式地声明这个const变量具有外部链接,这样编译器就会为这个const变量分配存储空间,允许其他源文件通过extern声明来访问这个变量。

编译器优化:将const值保存在符号表中:当涉及到const变量时,如果它们不被取地址(即没有使用&操作符获取其地址)并且被视为常量表达式,编译器通常会尝试优化这些变量的处理方式。具体来说,编译器可能会选择不在内存中为这些const变量分配实际的存储空间,而是直接将其值保存在符号表中,并在编译时替换所有对这个const变量的引用为其实际值。

Run-time constants

数组的定义时的长度值必须在编译期就已知,所以宏定义中的常数可以作为数组长度,而int n; int a[n]这样的语法就是错误的,不过现在似乎有了编译器优化,在Dev-cpp中这样写也可以通过编译

const int size=100;
int a[size];//OK

int x;
cin>>x;
const int size=x;
int a[size];//Error!!!

Pointers with const

注意区别下面两个式子

  • 常量指针:char * const p = "abc"; 不能赋予这个指针新的地址,相当于地址是const类型,但是p指向的值可以改变

  • const char* p = "abc"; 这种情况下p指向的是一个const char类型的值,因此指向的值不能改变,而指针指向的对象可以改变

//第一种情况是q是一个const指针,但是q指向的东西可以变
char * const q ="abc";
*q='c'; // OK
q++; // Error!
//第二种情况下,(*q)是一个const的值,此时q所指向的值不能变
const char *p = "abc";
*p='c';//error!
//区别这三种东西
string p1="zyc";
const string *p=&p1;
string const* p=&p1;//与第一个式子是相同的
string *const p=&p1;

不能将const类型的变量的地址赋值给非const变量的指针,因为可能会带来const变量的改变,这是const类型的变量不允许的

int i;
const int ci = 3;
int* ip;
const int* cip;
ip = &i;
ip = &ci; // Error, 因为ci是const而ip指向的对象并不是const
cip = &i;
cip = &ci;
*ip = 54; // always legal
*cip = 54; // never legal

可以把非const类型的值赋给对应的const型变量,函数中可以将参数设置为const类型表明这些参数在函数中不能被修改原本的值,也可以将返回值类型设置为const表示返回值不能被修改

void f1 (const int i) {
 i++; // illegal: compile-time error
}
int f3() { return 1; }
const int f4() { return 1; }
int main() {
 const int j = f3(); // works fine
 int k = f4(); // this works fine too
}

关于函数返回值 const 的使用

这样使用返回值的方法是对的,这只是一个拷贝

const int f() { return 5; }

int main() {
    int x = f(); // ✅ OK: 拷贝一个 const 返回值到非 const 变量中
}

但是如果我们尝试修改 const 返回值的时候,就会出现编译错误;或者将 const 返回值绑定到非 const 引用的时候,也会出现编译错误

const int f() { return 5; }

int main() {
    f() = 10; // ❌ 编译错误!不能给一个 const 临时对象赋值
}
const int f() { return 5; }

void foo(int& x) {}

int main() {
    foo(f()); // ❌ 编译错误!不能把 const 临时值绑定到非 const 引用
}

String literals

char * s="Hello World!";实质上是const char *类型,不要去修改s中的内容,这是一种未定义的行为(undefined behavior)

最好写成 char s[]="Hello World!";

C++ 编译器为了节省内存,会对相同的字符串字面量进行优化,将它们合并到同一个内存地址上。这叫做 字符串池(string pooling)字符串驻留(string interning)。也就是说,虽然你写了两次 "Hello World",但编译器可能只会为它分配一次内存空间,然后让 s1s2 都指向这个唯一的实例。

#include<iostream>
using namespace std;

int main()
{
    const char *s1="Hello World";
    const char *s2="Hello World";

    cout<<(void*)s1<<endl;
    cout<<(void*)s2<<endl;
    return 0;
    //输出的结果是s1和s2的地址,他们的结果是一样的
}

Conversions

Can always treat a non-const value as const

void f(const int* x); 
int a = 15;
f(&a); // ok
const int b = a;

f(&b); // ok
b = a + 1; // Error!

You cannot treat a constant object as non-constant without an explicit cast const_cast,不可以把 const 当作 non-const 来使用,除非进行转换。

评论区

对你有帮助的话请给我个赞和 star => GitHub stars
欢迎跟我探讨!!!