动态内存
C++ 程序中的内存分为两个部分:
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
很多时候,您无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。
在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new
运算符。
如果您不再需要动态分配的内存空间,可以使用 delete
运算符,删除之前由 new 运算符分配的内存。
malloc() 函数在 C 语言中就出现了,在 C++ 中仍然存在,但建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。
new 关键字
new data-type;
在这里,data-type 可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。
#include
using namespace std;
int main() {
double *pvalue = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量请求内存
// 如果自由存储区已被用完,可能无法成功分配内存。
// 所以建议检查 new 运算符是否返回 NULL 指针,并采取以下适当的操作
double *pvalue2 = NULL;
if (!(pvalue2 = new double)) //为NULL
{
cout << "Error: out of memory." << endl;
exit(1);
}
// 在分配的地址存储值
*pvalue = 29494.99;
cout << "Value of pvalue : " << *pvalue << endl;
delete pvalue;// 释放内存
delete pvalue2;
}
void test_array() {
const int m = 3;
const int n = 3;
/*一维数组*/
// 动态分配,数组长度为 m
int *array = new int[m];
//释放内存
delete[] array;
/*二维数组*/
int **array2;
// 假定数组第一维长度为 m, 第二维长度为 n
// 动态分配空间
array2 = new int *[m];
for (int i = 0; i < m; i++) {
array2[i] = new int[n];
}
//释放
for (int i = 0; i < m; i++) {
delete[] array2[i];
}
delete[] array2;
}
对象的动态内存分配
class Box {
public:
int num;
Box() {
cout << "调用构造函数!" << endl;
}
Box(int num) {
this.num = num;
cout << "调用构造函数!" << endl;
}
~Box() {
cout << "调用析构函数!" << endl;
}
};
void test_object() {
//形式1,声明一个 box1,类型为 Box
Box box1;// 直接声明一个对象,相当于box1()
//Box box2(1);//隐式调用A带一个参数的构造函数
//Box box3 = Box(2);//显式调用A带一个参数构造函数
//形式2
Box *myBox = new Box;
delete myBox;
//新建对象数组
Box *myBoxArray = new Box[4];
delete[] myBoxArray; // 删除数组
}
形式1:调用对应的构造函数在栈中创建对象,使用完毕后,系统自动回收对象内存,无需手动释放。
形式2:在堆内存中动态开辟空间创建对象,需要手动释放内存。
C 语言内存管理
C 语言为内存的分配和管理提供了几个函数。这些函数可以在
序号 | 函数和描述 |
---|---|
1 | void *calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。 |
2 | void free(void *address); 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。 |
3 | void *malloc(int num); 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。 |
4 | void *realloc(void *address, int newsize); 该函数重新分配内存,把内存扩展到 newsize。 |
5 | free() 释放分配的内存 |
注意:void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。C 语言中 void* 详解及应用
#include
#include
#include
//
// Created by rentianxin on 19-3-27.
//
int main() {
char name[100];
char *description;
strcpy(name, "Zara Ali");
/*动态分配内存*/
description = (char *) malloc(30 * sizeof(char));
//可以替换为如下的形式
// description = (char *) calloc(200, sizeof(char));
//判断有没有分配成功
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcpy( description, "Zara ali a DPS student in class 10th");
}
printf("Name = %s\n", name );
printf("Description: %s\n", description );
/* 假设您想要存储更大的描述信息 */
/*不重新分配额外的内存,strcat() 函数会生成一个错误,因为存储 description 时可用的内存不足*/
description = (char *) realloc( description, 100 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcat( description, " She is in class 10th");
}
printf("Name = %s\n", name );
printf("Description: %s\n", description );
/* 使用 free() 函数释放内存 */
free(description);
}
信号处理
信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。
有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++ 头文件
信号 | 描述 |
---|---|
SIGABRT | 程序的异常终止,如调用 abort。 |
SIGFPE | 错误的算术运算,比如除以零或导致溢出的操作。 |
SIGILL | 检测非法指令。 |
SIGINT | 接收到交互注意信号。 |
SIGSEGV | 非法访问内存。 |
SIGTERM | 发送到程序的终止请求。 |
signal() 函数--处理信号
C++ 信号处理库提供了 signal 函数,用来捕获突发事件。
void (*signal (int sig, void (*func)(int)))(int);
这个函数接收两个参数:第一个参数是一个整数,代表了信号的编号;第二个参数是一个指向信号处理函数的指针。
raise() 函数--生成信号
您可以使用函数 raise() 生成信号,该函数带有一个整数信号编号作为参数,语法如下:
int raise (signal sig);
在这里,sig 是要发送的信号的编号,这些信号包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。
#include
#include
#include
using namespace std;
void signalHandler (int signum) {
cout << "Interrupt signal (" << signum << ") received.\n";
// 清理并关闭
// 终止程序
exit(signum);
}
int main ()
{
// 注册信号 SIGINT 和信号处理程序
signal(SIGINT, signalHandler);
int i = 0;
while(++i){
cout << "Going to sleep...." << endl;
if( i == 3 ){
raise( SIGINT);
}
sleep(1);/*linux 让线程挂起,使用sleep,参数为秒*/
}
return 0;
}
多线程
多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。
假设您使用的是 Linux 操作系统,我们要使用 POSIX 编写多线程 C++ 程序。POSIX Threads 或 Pthreads 提供的 API 可在多种类 Unix POSIX 系统上可用,比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。
#include
线程Id
typedef unsigned long int pthread_t; //come from /usr/include/bits/pthread.h
用途:pthread_t用于声明线程ID。
sizeof (pthread_t) =4;
创建线程
#include
pthread_create (thread, attr, start_routine, arg)
pthread_create 创建一个新的线程,并让它可执行。下面是关于参数的说明:
参数 | 描述 |
---|---|
thread | 指向线程标识符指针。 |
attr | 一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。 |
start_routine | 线程运行函数起始地址,一旦线程被创建就会执行。 |
arg | 运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。 |
创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败。
终止线程
#include
pthread_exit (status)
pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。
如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。
#include
// 必须的头文件
//cmake LINK_LIBRARIES(pthread)
//编译 g++ test.cpp -lpthread -o test.o
#include
using namespace std;
#define NUM_THREADS 5
// 线程的运行函数
void *say_hello(void *args) {
cout << "Hello Runoob!" << endl;
return 0;
}
void *printHello(void *threadid) {
// 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
int tid = *((int *) threadid);
cout << "Hello Runoob! 线程 ID, " << tid << endl;
pthread_exit(NULL);
}
int main() {
// 定义线程的 id 变量,多个变量使用数组
pthread_t tids[NUM_THREADS];
//不传参
// for (int i = 0; i < NUM_THREADS; ++i) {
// //参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
// int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
// if (ret != 0) {
// cout << "pthread_create error: error_code=" << ret << endl;
// }
// }
//传参
pthread_t threads[NUM_THREADS];
int indexes[NUM_THREADS];// 用数组来保存i的值
int rc;
int i;
for (i = 0; i < NUM_THREADS; i++) {
cout << "main() : 创建线程, " << i << endl;
indexes[i] = i; //先保存i的值
// 传入的时候必须强制转换为void* 类型,即无类型指针
rc = pthread_create(&threads[i], NULL, printHello, (void *) &(indexes[i]));
if (rc) {
cout << "Error:无法创建线程," << rc << endl;
exit(-1);
}
}
//等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
pthread_exit(NULL);
}
向线程传递多参数
这个实例演示了如何通过结构传递多个参数。您可以在线程回调中传递任意的数据类型,因为它指向 void。
#include
#include
#include
using namespace std;
#define NUM_THREADS 5
struct thread_data {
int thread_id;
char *message;
};
void *PrintHello(void *threadarg) {
struct thread_data *my_data;
my_data = (struct thread_data *) threadarg;
cout << "Thread ID : " << my_data->thread_id;
cout << " Message : " << my_data->message << endl;
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
struct thread_data datas[NUM_THREADS];
int rc;
int i;
for (i = 0; i < NUM_THREADS; i++) {
cout << "main() : creating thread, " << i << endl;
datas[i].thread_id = i;
datas[i].message = (char *) "This is message";
rc = pthread_create(&threads[i], NULL, PrintHello, (void *) &datas[i]);
if (rc) {
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
连接和分离线程
ref:在Linux中使用线程
我们可以使用以下两个函数来连接或分离线程:
pthread_join (threadid, status) //阻塞当前线程的执行,直到threadid指定的线程完成了
pthread_detach (threadid) //将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源
pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止。当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。
joinable线程可在创建后,用pthread_detach()显式地分离。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连接。
这个实例演示了如何使用 pthread_join() 函数来等待线程的完成。
#include
#include
#include
#include
using namespace std;
#define NUM_THREADS 5
void *wait(void *t) {
int i;
long tid;
tid = (long) t;
sleep(1);
cout << "Sleeping in thread " << endl;
cout << "Thread with id : " << tid << " ...exiting " << endl;
pthread_exit(NULL);
}
int main() {
int rc;
int i;
pthread_t threads[NUM_THREADS];
/*typedef union pthread_attr_t pthread_attr_t;*/
pthread_attr_t attr;
void *status;
// 初始化并设置线程为可连接的(joinable)
/*extern int pthread_attr_init (pthread_attr_t *__attr) __THROW __nonnull ((1));*/
pthread_attr_init(&attr);
/*extern int pthread_attr_setdetachstate (pthread_attr_t *__attr,
int __detachstate)
__THROW __nonnull ((1));*/
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for (i = 0; i < NUM_THREADS; i++) {
cout << "main() : creating thread, " << i << endl;
rc = pthread_create(&threads[i], NULL, wait, (void *) &i);
if (rc) {
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
}
// 删除属性,并等待其他线程
pthread_attr_destroy(&attr);
for (i = 0; i < NUM_THREADS; i++) {
/*The exit status of the thread is stored in *THREAD_RETURN (status), if THREAD_RETURN is not NULL.*/
rc = pthread_join(threads[i], &status);
if (rc) {
cout << "Error:unable to join," << rc << endl;
exit(-1);
}
cout << "Main: completed thread id :" << i;
cout << " exiting with status :" << status << endl;
}
cout << "Main: program exiting." << endl;
pthread_exit(NULL);
}
join是三种同步线程的方式之一。另外两种分别是互斥锁(mutex)和条件变量(condition variable)。
C++ 11 之后标准的线程库
略,有兴趣看看例子
#include
#include
std::thread::id main_thread_id = std::this_thread::get_id();
void hello() {
std::cout << "Hello Concurrent World\n";
if (main_thread_id == std::this_thread::get_id())
std::cout << "This is the main thread.\n";
else
std::cout << "This is not the main thread.\n";
}
void pause_thread(int n) {
std::this_thread::sleep_for(std::chrono::seconds(n));
std::cout << "pause of " << n << " seconds ended\n";
}
int main() {
std::thread t(hello);
std::cout << t.hardware_concurrency() << std::endl;//可以并发执行多少个(不准确)
std::cout << "native_handle " << t.native_handle() << std::endl;//可以并发执行多少个(不准确)
t.join();
std::thread a(hello);
a.detach();
std::thread threads[5];// 默认构造线程
std::cout << "Spawning 5 threads...\n";
for (int i = 0; i < 5; ++i)
threads[i] = std::thread(pause_thread, i + 1); // move-assign threads
std::cout << "Done spawning threads. Now waiting for them to join:\n";
for (auto &thread : threads)
thread.join();
std::cout << "All threads joined!\n";
}
线程的同步
互斥锁
获得资源的线程排斥其它没有获得资源的线程。
互斥锁在Linux中的名字是mutex,在系统底层机制叫futex。对于提供给用户使用的则是这个mutex。
Linux初始化和销毁互斥锁的接口是pthread_mutex_init()和pthead_mutex_destroy(),对于加锁和解锁则有pthread_mutex_lock()、pthread_mutex_trylock()和pthread_mutex_unlock()。
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destory(pthread_mutex_t *mutex );
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
条件变量
条件变量是一种事件机制。由一类线程来控制“事件”的发生,另外一类线程等待“事件”的发生。为了实现这种机制,条件变量必须是共享于线程之间的全局变量。而且,条件变量也需要与互斥锁同时使用。
初始化和销毁条件变量的接口是pthread_cond_init()和pthread_cond_destory();控制“事件”发生的接口是pthread_cond_signal()或pthread_cond_broadcast();等待“事件”发生的接口是pthead_cond_wait()或pthread_cond_timedwait()。
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_destory(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex, const timespec *abstime);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);