1. C语言 和 C++的关系
- C++ 包含了C语言大部分语法,这样c++能轻而易举的使用业界多年积累的C语言优秀的库
- C++增加了面向对象编程的语法,让C++能更轻松表达面向对象思想编程,面向对象编程思想跟语言无关,用C语言也能实现面向对象思想编程,只是表达起来比较费劲,
2. 指针变量和普通变量
2.1 指针的特点
在C++中,指针变量有以下特点:
- 指针变量存储的是内存地址,而不是数据本身。
- 指针变量可以指向任何数据类型,包括整数、浮点数、字符、数组、结构体、类等。
- 指针变量可以通过解引用运算符
*
来访问其指向的数据。 - 指针变量可以通过指针运算符
++
和--
来移动指针的位置。 - 指针变量可以被用于动态分配内存,例如使用
new
运算符创建对象。 - 指针变量可以被用于传递参数,例如使用指针变量作为函数参数来修改函数外部的变量。
- 如果使用new运算符动态分配内存来创建指针变量,则必须使用delete运算符手动释放内存
以下是使用指针变量的示例:
#include <iostream>
int main() {
int x = 10;
int* ptr = &x;
std::cout << "Value of x: " << x << std::endl;
std::cout << "Address of x: " << &x << std::endl;
std::cout << "Value of ptr: " << ptr << std::endl;
std::cout << "Address of ptr: " << &ptr << std::endl;
std::cout << "Value of *ptr: " << *ptr << std::endl;
return 0;
}
在上面的示例中,我们定义了一个名为x
的整数变量,并使用取地址运算符&
将其地址存储在指针变量ptr
中。然后,我们使用std::cout
语句输出x
的值、x
的地址、ptr
的值、ptr
的地址和*ptr
的值,分别表示整数变量的值、整数变量的地址、指针变量的值、指针变量的地址和指针变量所指向的值。
2.2 指针变量和普通变量有以下区别:
- 普通变量存储的是数据本身,而指针变量存储的是内存地址。
- 普通变量可以直接访问其存储的数据,而指针变量需要通过解引用运算符
*
来访问其指向的数据。 - 普通变量在声明时会自动分配内存空间,而指针变量需要使用
new
运算符或分配静态存储空间来动态分配内存空间。 - 普通变量的大小由其数据类型决定,而指针变量的大小通常是固定的,通常为4字节或8字节,具体取决于编译器和操作系统的位数。
以下是使用普通变量和指针变量的示例:
#include <iostream>
int main() {
int x = 10;
int* ptr = &x;
std::cout << "Value of x: " << x << std::endl;
std::cout << "Value of ptr: " << ptr << std::endl;
std::cout << "Value of *ptr: " << *ptr << std::endl;
return 0;
}
在上面的示例中,我们定义了一个名为x
的整数变量,并使用取地址运算符&
将其地址存储在指针变量ptr
中。然后,我们使用std::cout
语句输出x
的值、ptr
的值和*ptr
的值,分别表示整数变量的值、指针变量的值和指针变量所指向的值。
3. 构造函数和拷贝构造函数
拷贝构造函数是一种特殊类型的构造函数,用于创建新对象并将其初始化为与现有对象相同的值。拷贝构造函数通常用于对象的复制和传递。当使用一个对象初始化另一个对象时,会调用拷贝构造函数。
以下是一个示例:
#include <iostream>
class A {
public:
A() {
std::cout << "A constructor" << std::endl;
}
A(const A& other) {
std::cout << "A copy constructor" << std::endl;
}
~A() {
std::cout << "A destructor" << std::endl;
}
};
int main() {
A a1; // 调用构造函数
A a2 = a1; // 调用拷贝构造函数
return 0;
}
在上面的示例中,我们定义了一个名为A
的类,并在其中定义了构造函数、拷贝构造函数和析构函数。在main()
函数中,我们创建了两个A
对象a1
和a2
,分别使用构造函数和拷贝构造函数进行初始化。这将调用A
类的拷贝构造函数来创建a2
对象,并将a1
对象的值复制到a2
对象中。
3.1 简要说明构造函数和析构函数调用时机
普通变量之间赋值 会调用拷贝构造函数,在函数运行完时,会回收资源,遵循先构造 后释放的原则,如下下图:
#include <iostream>
class A {
public:
A() {
std::cout << "A constructor" << std::endl;
}
A(const A& other) {
std::cout << "A copy constructor" << std::endl;
}
~A() {
std::cout << "A destructor" << std::endl;
}
};
A foo(A a) {
std::cout << "Inside foo()" << std::endl;
A& b = a;
return b;
}
int main() {
A a1; // 调用构造函数
A a2 = a1; // 调用拷贝构造函数
foo(a2); // 调用拷贝构造函数
return 0;
}
如果使用指针指向的对象是通过new
运算符动态分配的内存,那么在不释放该内存的情况下,该内存将一直存在,直到程序结束。指针指向的对象不会自动被释放。
例如,以下代码中动态分配的整数数组不会自动被释放:
int* arr = new int[10];
如果不使用delete[]
运算符来释放该数组,则该数组将一直存在,直到程序结束。
需要注意的是,在C++11及以上版本中,可以使用智能指针(如std::unique_ptr
和std::shared_ptr
)来管理动态分配内存,从而避免内存泄漏和手动释放内存的问题。智能指针会在对象不再需要时自动释放所占用的内存,从而提高程序的安全性和可维护性。
以下是使用std::unique_ptr
和std::shared_ptr
管理动态分配内存的示例:
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int[]> arr1(new int[10]); // 使用 std::unique_ptr 管理动态分配的数组
std::shared_ptr<int[]> arr2(new int[10]); // 使用 std::shared_ptr 管理动态分配的数组
return 0;
}
在上面的示例中,我们使用std::unique_ptr
和std::shared_ptr
分别管理动态分配的整数数组。当arr1
和arr2
不再需要时,它们会自动释放所占用的内存。
3.2 拷贝构造函数调用时机
拷贝构造函数是在以下情况下被调用:
-
使用一个对象初始化另一个对象时,如
A a1; A a2 = a1;
-
将一个对象作为参数传递给函数时,如
void foo(A a) { ... }
-
在函数中返回一个对象时,如
A foo() { ... return a; }
-
一个对象构造另外一个对象,如
A a1(10); A a2(a1,3);
4. 引用和指针的区别
在C++中,引用和指针都是用于访问内存地址的机制,但它们有以下区别:
- 引用是一个别名,指针是一个变量。引用在定义时必须初始化,而指针可以在任何时候指向不同的对象。
- 引用不能为
NULL
或nullptr
,而指针可以为NULL
或nullptr
,表示未指向任何对象。 - 在函数中传递引用参数可以避免复制对象的开销
以下是使用引用和指针的示例:
#include <iostream>
void foo(int& x, int* ptr) {
x++;
(*ptr)++;
}
int main() {
int x = 10;
int* ptr = &x;
int& x1 = x;
std::cout << "Value of x before: " << x << std::endl;
std::cout << "Value of ptr before: " << *ptr << std::endl;
foo(x1, ptr);
std::cout << "Value of x after: " << x << std::endl;
std::cout << "Value of ptr after: " << *ptr << std::endl;
return 0;
}
注意点: int* arr = new int[10]; // 释放数组 delete[] arr;
delete[] arr 释放数组,不能使用 delete arr,delete运算符只会释放数组的第一个元素,而不是整个数组