需要注意
- 将类型指针看做一个新的数据类型,存放常量或者变量的地址
=
两端数据类型需要相同&
放在变量前取地址,可赋值给相同类型的指针变量前- 指针类型变量前加
*
获得地址中的数据
指针运算与数组
数组 int a[100]={0}
数组名就是第一个元素的地址,所以可以赋值给指针
int a[100];
int * iptr = a;
此时
a[i]
、*(a+i)
、iptr[i]
、*(iptr+i)
等同,等于第i个元素的值
而且
&a[i]
、iptr+i
、&iptr[i]
、a+i
、等同,等于第i个元素的地址
注意数组名为指针常量,不可被赋值
注意指针只支持加减运算
关于sizeof()
在C++语言中,sizeof
是一个用于获取数据类型或对象的内存大小(以字节为单位)的运算符。它通常以如下的方式使用:
sizeof(数据类型或对象)
以下是一些关于 sizeof
运算符的重要信息:
-
用法:
sizeof
运算符可以用于任何数据类型、变量、或者表达式,它返回一个size_t
类型的值,表示其操作数所占用的内存大小。 -
示例:以下是一些示例用法:
int sizeOfInt = sizeof(int); // 获取整数int的大小 double sizeOfDouble = sizeof(double); // 获取双精度浮点数double的大小 char myChar = 'A'; int sizeOfChar = sizeof(myChar); // 获取字符char变量myChar的大小
-
不同数据类型的大小:不同的数据类型在不同的编译器和平台上可能有不同的大小。通常情况下,
sizeof
的结果取决于编译器和目标平台的架构。例如,在大多数系统上,sizeof(int)
通常为4字节,sizeof(double)
通常为8字节。 -
结构和类的大小:
sizeof
运算符也可以用于结构体和类,它将返回这些自定义类型的实例所占用的内存大小。结构体和类的大小通常是其成员变量大小之和,但也可能受到编译器的优化和对齐方式的影响。 -
运行时和编译时:
sizeof
运算符在编译时计算大小,因此它的结果是一个常量表达式。这使得它非常适用于数组大小的计算,例如int myArray[10];
的大小可以使用sizeof(myArray)
来获取。
运用
int myarray[] = {1,2,3,5,6,8}
int array_num = sizeof(myarray)/sizeof(*myarray); //数组中元素的个数
注意
数组名和数组第一个元素的指针并不等同
#include "basic.h"
int8 i = 0;
int array[10] = {0};
int * iptr = array;
int main(){
cout<<"array.size="<<sizeof(array)/sizeof(*array)<<endl;
cout<<"array.size="<< sizeof(iptr)<<endl;
return 0;
}
堆区、栈区、静态区、常量区、代码区以及内存的申请与释放
C++ 程序运行时的内存分为不同的区域,包括栈区、堆区、静态区、常量区、代码区。以下是对每个区域的详细解释:
-
栈区(Stack Area):
- 栈区是用于存储函数调用和局部变量的地方。
- 每次函数被调用时,函数的局部变量会被分配到栈上。
- 栈区的内存管理由编译器自动完成,当函数退出时,分配给局部变量的内存会被释放。
- 栈上的内存分配和释放是快速的,但生命周期有限,函数退出时局部变量就会销毁。
- 栈区内存通常有限,不适合存储大型对象或长时间存活的数据。
-
堆区(Heap Area):
- 堆区用于存储动态分配的内存。
- 堆上的内存分配和释放是手动控制的,通常使用
new
和delete
或malloc
和free
函数来完成。 - 堆区的内存生命周期由程序员控制,可以在需要时分配内存,并在不再需要时释放它。
- 堆区适用于需要在程序的不同部分之间共享数据的情况,但需要谨慎管理以防止内存泄漏或访问非法内存。
-
静态区(Static Area):
- 静态区用于存储全局变量、静态变量和常量。
- 静态变量的内存分配在程序启动时完成,并在程序运行期间一直存在。
- 全局变量和静态变量通常存储在静态区,并在整个程序生命周期内保持不变。
- 静态区的内存由编译器分配和管理。
-
常量区(Constant Area):
- 常量区用于存储字符串字面值和常量数据。
- 字符串字面值(例如:“Hello, World!”)存储在常量区。
- 常量区的数据通常在程序启动时加载,并在整个程序运行期间保持不变。
- 常量区的内存由编译器分配和管理。
-
代码区(Code Area):
- 代码区是存储程序的可执行代码的地方。
- 它包含程序的二进制指令,用于执行函数和操作。
- 代码区通常是只读的,不允许写入。
- 程序的执行从代码区开始。
堆区内存的申请与释放
1. malloc()
与free()
void* malloc(size_t size)
申请size
大小的堆内存区域,返回没有类型的指针
#include <cstdlib>
int asize = 8;
int *ap = (int *)malloc(asize * sizeof(int)); //申请内存
此时 ap
可看做有8
个整形类型元素的数组
当申请的堆内存不够分配时将返回空指针NULL
void free(void *)
参数为之前调用 malloc()
时返回的地址
free(ap); //释放内存
2. new
与 delete
C++ 中的 new
和 delete
是用于动态内存分配和释放的运算符。它们允许程序在运行时请求内存,而不是在编译时确定内存大小。
-
new 运算符:
new
运算符用于在堆区(Heap Area)动态分配内存,并返回分配内存的地址。- 可以用于分配单个对象或数组。
- 使用
new
运算符时,需要指定要分配的数据类型,如new int
或new MyClass
。 - 示例:
int *ptr = new int; // 分配一个整数的内存 MyClass *obj = new MyClass; // 分配一个自定义类的对象的内存
-
delete 运算符:
delete
运算符用于释放由new
分配的动态内存。- 必须与
new
配对使用,否则会导致内存泄漏。 - 使用
delete
时,需要指定要释放的内存的指针。 - 示例:
delete ptr; // 释放整数的内存 delete obj; // 释放自定义类对象的内存
-
new[] 运算符和 delete[] 运算符:
- 除了分配单个对象的内存外,
new
和delete
还支持分配和释放数组的内存。 - 使用
new[]
分配数组内存,使用delete[]
释放它。 - 示例:
int *arr = new int[10]; // 分配一个包含10个整数的数组的内存 delete[] arr; // 释放整数数组的内存
- 除了分配单个对象的内存外,
-
异常安全性:
- 使用
new
时,如果分配内存失败,它将抛出std::bad_alloc
异常。 - 在分配内存后,应始终检查指针是否为
nullptr
,以确保内存分配成功。 - 使用
delete
时,应该确保指针不为空,否则可能导致未定义行为。
- 使用
-
智能指针替代:
- C++11引入了智能指针(如
std::shared_ptr
和std::unique_ptr
),它们提供更安全的内存管理,减少了手动管理内存的需要,有助于避免内存泄漏和释放已释放内存的问题。
- C++11引入了智能指针(如
指向常量的指针、指针常量、指向常量的指针常量
const int a = 78;
const int * pi = &a; //指向常量的指针
int * const pc = 18; //指针常量
*pc = 16; //可以修改指针指向的数据
p = &a //! error 不能修改常量pc
const int ci = 66;
const int * const pd = &ci; //指向常量的指针常量,指针 pd 和指针指向的区域 *pd 均不能修改
空类型指针 void *
void *
仅表示存放一个地址,不指向任何类型不能进行指针运算,也不能间接引用
int a = 20;
int * pr = &a;
void * p = pr; // 可以将其他类型的指针赋值给空类型指针
pr = p; // error 不能将空类型指针赋值给其他类型指针
pr = (int *) p; // 可以显式转换赋值
字符串与字符指针
字符串为双括号括起来的字符序列
char buffer[] = "hello";
char buffer[] = {'h','e','l','l','0','\0'};
以上两种方法等同,因此可以将字符串当做字符数组来看待 buffer
即第一个元素的首地址
char *pc = "hello";
cout<<pc<<endl; // hello
cout<<*pc<<endl; //h
cout<<*(pc+1)<<endl;//e
指针数组
char * proname[] = {"Lihua","Luke","Alice"};
char name[3][8] = {"Lihua","Luke","Alice"};
节省存储空间
字符串比较
由于字符串的地址特性,因此字符串比较不能直接使用 ==
,可以使用 strcmp()
char * p1 = "hello";
char * p2 = "hello";
char * p3 = "helle";
cout<<p1==p2<<endl; // false
cout<<strcmp(p1,p2)<<endl; // 0
实际上编译器可能会对此做出优化,在 Clion
中的结果如下
#include "basic.h"
#include <cstring>
int8 i = 0;
int main(){
char buffer1[6] = "hello";
char buffer2[6] = "hello";
char *p1 = "hello";
char *p2 = "hello";
cout<<((buffer1==buffer2)?"equal":"not equal")<<endl; //not equal
cout<<((p1==p2)?"equal":"not equal")<<endl; // equal
cout<<((strcmp(buffer1,buffer2)==0)?"equal":"not equal")<<endl; //equal
return 0;
}
命令行参数
C++ 程序可以通过命令行参数来接收外部输入,这些参数是在程序运行时从命令行传递给程序的信息。命令行参数允许在不更改程序代码的情况下传递配置选项、文件名、运行参数等信息给程序。
在C++中,主函数 main
可以接受两个参数:argc
和 argv
。这两个参数分别表示命令行参数的数量和参数数组。
-
argc(Argument Count):
argc
是一个整数,表示命令行参数的数量。- 它包括程序名(可执行文件名)作为第一个参数。
- 如果没有提供任何参数,
argc
的值将为1,因为至少会有一个参数,即程序本身的名称。
-
argv(Argument Vector):
argv
是一个指向字符指针数组的指针,每个指针指向一个命令行参数的字符串。argv[0]
指向程序本身的名称(可执行文件名)。argv[1]
、argv[2]
、…、argv[argc-1]
分别指向额外的命令行参数字符串。argv[argc]
指向空指针(nullptr
),用于表示参数的结束。- 空指针为假 即
while(NULL)
为假,可用于遍历参数。
以下是一个简单的示例,演示如何使用 argc
和 argv
来访问命令行参数:
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << "Total number of command line arguments: " << argc << std::endl;
// 输出所有命令行参数
for (int i = 0; i < argc; ++i) {
std::cout << "Argument " << i << ": " << argv[i] << std::endl;
}
return 0;
}
在上述示例中,argc
的值表示传递给程序的命令行参数数量,argv
数组包含这些参数的字符串。
例如,如果运行程序如下:
./my_program arg1 arg2 arg3
则程序将输出:
Total number of command line arguments: 4
Argument 0: ./my_program
Argument 1: arg1
Argument 2: arg2
Argument 3: arg3
函数指针
函数名也是地址,指向代码区
函数指针是C++中非常有用和强大的特性之一。它们允许您在运行时动态选择要调用的函数,从而增强了程序的灵活性。
-
函数指针的定义:
函数指针是一个指向函数的指针变量。它的声明通常如下所示:
return_type (*function_pointer)(parameter_type1, parameter_type2, ...);
return_type
:函数返回的类型。function_pointer
:函数指针变量的名称。parameter_type1, parameter_type2, ...
:函数的参数类型。
例如,以下是一个函数指针声明,指向一个接受两个整数参数并返回整数的函数:
int (*add)(int, int);
-
初始化函数指针:
函数指针可以初始化为指向特定函数的地址。例如,将上述的
add
函数指针初始化为指向一个加法函数的地址:int sum(int a, int b) { return a + b; } add = sum; // 初始化函数指针为指向 sum 函数的地址
-
通过函数指针调用函数:
使用函数指针调用函数与直接调用函数类似,只需像调用普通函数一样使用函数指针即可:
int result = add(3, 4); // 使用函数指针调用 sum 函数
请注意,函数指针的使用方式与函数名相同,这使得它们可以在运行时动态选择要调用的函数。
-
函数指针作为参数:
函数指针还可以作为函数的参数,这样可以实现函数回调或在运行时切换不同的实现。例如:
void performOperation(int a, int b, int (*operation)(int, int)) { int result = operation(a, b); std::cout << "Result: " << result << std::endl; } int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int main() { performOperation(5, 3, add); // 调用 performOperation 使用 add 函数 performOperation(5, 3, subtract); // 调用 performOperation 使用 subtract 函数 return 0; }
在上述示例中,
performOperation
函数接受一个函数指针作为参数,可以根据传递的函数指针调用不同的操作函数。 -
函数指针数组:
您还可以创建函数指针数组,其中每个元素都是指向不同函数的函数指针,以便在运行时选择要调用的函数。
函数指针需要与被调用函数类型保持一致