在C++中delete 与 delete [] 是有区别的,主要体现在引用数据类型和基础数据类型,类也算是引用数据类型。有时候定义一个数组或者类指针,在删除指针的时候,最好用delete [],否则就有可能发生内存泄漏。为什么呢
C++ 中我们常用new-delete来分配和释放资源,而new一个对象或者一个对象数组都是允许的。
动态创建对象只需要指定其数据类型和数据长度,不必为改对象命名。动态创建完的对象我们应该显示地销毁改对象,释放其占用的内存。由此我们将用到delete 或者delete来销毁对象。
关于对象销毁又分为两种情况:一是为基本数据类型分配和释放资源,一是为自定义对象分配和释放资源。
(1)针对基本数据类型
int* a = new int[10];
delete a;
delete[] a;
这两种方式释放的效果一样。分配简单类型内存时,内存大小已经确定,系统可以记忆并且进行管理,在析构时,系统并不会调用析构函数。(参考网上博客分析).
(2)针对自定义对象
通过调用类对象的析构函数释放用户分配的空间。
// DemoDelete.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
using namespace std;
class Temp{
public:
Temp(){
cout<<"gouzao"<<endl;
}
~Temp(){
cout<<"xigou"<<endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
int* a = new int[10];
//delete a;//OK
delete[] a;//OK
Temp* arrayTemp = new Temp[5];
cout<<"arraytemp: "<<arrayTemp<<endl;
delete[] arrayTemp;
cout<<"******************"<<endl;
Temp* arrayTemp2 = new Temp[5];
cout<<"arraytemp2: "<<arrayTemp2<<endl;
delete arrayTemp2;
system("pause");
return 0;
}
运行 结果:
通过结果可以看到:在自定义对象上delete 和delete[] 的区别。delete arrayTemp在回收空间的过程中,只有arrayTemp[0] 这个对象调用了其析构函数,其它对象如arrayTemp[1]、arrayTemp[2] 等都没有调用自身的析构函数。所以资源并没有释放完。而delete[]则保证了所有对象都调用了析构函数进行资源的释放。
c++中的泛型可以自动推导,也可以显示指定,最好显示指定,自动推导有可能出错。
C++中如何交换两个变量的值?
你注意到了么?除了类型不同,函数体代码完全相同!!!
C++强调代码复用!那如何解决这个代码冗余的问题呢?
泛型编程
泛型编程的概念:不考虑具体数据类型的编程模式。
C++中,泛型编程是通过函数模板。1. 函数模板是一种特殊的函数可用不同类型进行调用;2. 函数模板看起来和普通函数很相似,区别是类型可被参数化;
函数模板的语法规则
template关键字用于声明开始进行泛型编程
typename关键字用于声明泛指类型
函数模板的应用
1. 自动类型推导调用
2. 具体类型显示调用
#include <cstdlib>
#include <iostream>
using namespace std;
template<typename T>
void Swap(T& a, T& b){
T t = a;
a = b;
b = t;
}
int main(int argc, char *argv[]){
int a = 1;
int b = 2;
Swap(a, b);
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
float fa = 3;
float fb = 4;
Swap<float>(fa, fb);
cout<<"fa = "<<fa<<endl;
cout<<"fb = "<<fb<<endl;
char ca = 'a';
char cb = 'b';
Swap(ca, cb);
cout<<"ca = "<<ca<<endl;
cout<<"cb = "<<cb<<endl;
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
编译运行结果如下:
通过泛型编程实现不同数据类型的排序算法。
#include <cstdlib>
#include <iostream>
using namespace std;
template<typename T>
void Swap(T& a, T& b){
T t = a;
a = b;
b = t;
}
template<typename T>
void SelectSort(T array[], int length){
for(int i=0; i<length; i++){
T min = array[i];
int index = i;
for(int j=i+1; j<length; j++){
if( array[j] < min ){
min = array[j];
index = j;
}
}
Swap(array[i], array[index]);
}
}
int main(int argc, char *argv[]){
int array[] = {3, 2, 5, 3 , 4};
SelectSort<int>(array, 5);
for(int i=0; i<5; i++){
cout<<array[i]<<endl;
}
char ca[] = {'b', 'c', 'a', 'e', 'd', 'f'};
SelectSort(ca, 6);
for(int i=0; i<6; i++){
cout<<ca[i]<<endl;
}
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
编译运行结果如下:
函数模板的深入理解
1. 编译器并不是把函数模板处理成能够处理任意类型的函数;
2. 编译器从函数模板通过具体类型产生不同的函数;
3. 编译器会对函数模板进行两次编译;
在声明的地方对模板代码本身进行编译;
在调用的地方对参数替换后的代码进行编译;
当函数模板遇上函数重载会发生什么?函数模板可以像普通函数一样被重载。
1. C++编译器优先考虑普通函数;
2. 如果函数模板可以产生一个更好的匹配,那么选择模板;
3. 可以通过空模板实参列表的语法限定编译器只通过模板匹配;
#include <cstdlib>
#include <iostream>
using namespace std;
int Max(int a, int b){
cout<<"int Max(int a, int b)"<<endl;
return a > b ? a : b;
}
template<typename T>
T Max(T a, T b){
cout<<"T Max(T a, T b)"<<endl;
return a > b ? a : b;
}
template<typename T>
T Max(T a, T b, T c){
cout<<"T Max(T a, T b, T c)"<<endl;
return Max(Max(a, b), c);
}
int main(int argc, char *argv[]){
int a = 1;
int b = 2;
cout<<Max(a, b)<<endl;
cout<<Max<>(a, b)<<endl;
cout<<Max(3.0, 4.0)<<endl;
cout<<Max(5.0, 6.0, 7.0)<<endl;
cout<<Max('a', 100)<<endl;
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
编译运行结果如下:
注:1. 函数模板不允许自动类型转化;2. 普通函数能够进行自动类型转换;
多参数函数模板
函数模板可以定义任意多个不同的类型参数。
多个类型参数的模板可以进行自动类型推导吗?当声明的类型参数为返回值类型时,无法进行自动类型推导。
将返回类型参数声明到第一个参数位置,调用时只需显示声明返回类型参数即可。
#include <cstdlib>
#include <iostream>
using namespace std;
template<typename RT, typename T1, typename T2>
RT Add(T1 a, T2 b){
return static_cast<RT>(a + b);
}
int main(int argc, char *argv[]){
cout<<Add<double, char, float>('a', 100.0f)<<endl;
cout<<Add<double>('a', 100.0f)<<endl;
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
编译运行结果如下:
小结:
1. 函数模板其实是一个具有相同行为的函数家族;
2. 函数模板可以根据类型实参对函数进行推导调用;
3. 函数模板可以显示的指定类型参数;
4. 函数模板可以被重载;
什么是常引用(两种初始化方法)
首先进一步理解引用: int &a=b 相当于 int *const a=b。即引用是一个指针常量(又称常指针,即一个常量,其类型是指针)。
每当编译器遇到引用变量a,就会自动执行 * 操作。
而常引用:const int &a=b就相当于 const int * const a=b。不仅仅是a这个地址不可修改,而且其指向的内存空间也不可修改。
什么是常引用.之一(引用变量)
#include<iostream>
using namespace std;
void main()
{
//(1)变量初始化,再const引用 变量
int b = 10;
const int &a = b;
b = 11;//b是可以修改的,但是a不能修改
printf("a=%d,b=%d\n", a, b);
system("pause");
}
什么是常引用.之二(引用常量)
#include<iostream>
using namespace std;
void main()
{
//(2)const引用 常量
const int &c = 15;
//编译器会给常量15开辟一片内存,并将引用名作为这片内存的别名
//int &d=15//err
system("pause");
}
我们为什么需要常引用(进阶应用)
#include<iostream>
using namespace std;
typedef struct _teacher
{
char name[32];
int age;
}teacher;
//引用本来就相当于一个常指针:* const t
//再加一个const表示指针指向的内存空间也不可修改
//作用:1.让变量所指向的内存空间只读 2.指向常量
//给const引用初始化有两种方法:参见什么是常引用(1)(2)
void getTeacher(const teacher &t)
{
//t.age = 32;//err
cout << "t.age=" << t.age << endl;
}
void main()
{
teacher t1;
t1.age = 25;
getTeacher(t1);
system("pause");
}
比如函数add中不能通过赋值改变a的值,只能其它变量来取a的值,但是我在add函数返回值的时候同样可以通过return b或者其它变量去改变常引用实参的值。
int add(const int &a){
//a = a+1; //error
return a;
}
下面来看c++指针常量和常量指针以及引用个坑。
int * const pt = "abc";
// 即 int (*(const pt)) = "abc";
// 指针pt的指向不可以改变,但是pt指向的值可以改变,这叫指针常量。
int const * pt = "abc";
// 即 int (const(* pt)) = "abc";
// 指针pt的指向可以改变,但是pt指向的值不可以改变,这叫常量指针。
int & b = a;
// 即 int (*(const pt)) = &a;
// 即 *pt == a;
// 即 b == * pt;
// 即 引用是指针常量,引用的本质是指针pt的指向不可以改变,但是pt指向的值可以改变。
const int & b = a;
// 即 const int (*(const pt)) = &a;
// 即 *pt == a;
// 即 b == *pt;
// 即 常引用是常指针常量,指针pt的指向不可以改变,pt指向的值也不可以改变。
// const int & b 等同于 int const & b
// int const * pt 等同于 const int * pt
// 主要是看 * 和 const 谁离指针近, (*pt)表示指针指向的值,
// int const (*pt) 和 const int (*pt) 都表示pt指针指向的值不可以改变,pt指针的指向可以改变。
看到一篇不错的文章,用c++写堆栈的例子,摘抄在下方。
堆栈是一种非常重要的数据结构,可以看成是一种插入和删除均在一端进行的线性表。因此,可以认为堆栈中的元素是“先进后出”。由于堆栈是一种特殊的线性表,而线性表有链表描述和数组描述两种,因此堆栈也可以使用链表描述和数组描述两种方法,同样的,堆栈可以使用线性表的类继承来实现,但是考虑到类继承的方式会降低堆栈的运行效率,本文所实现的堆栈没有考虑这个方式。
一个堆栈应该具备如下的功能:
1.创建一个堆栈;
2.清空一个堆栈;
3.检查一个堆栈是否非空;
4.得到堆栈的长度;
5.得到最近一个入栈的元素;
6.出栈;
7.入栈一个元素;
8.复制一个堆栈;
9.遍历输出堆栈元素
因此,使用链表构建堆栈的类定义如下:
//
// stackcreat.hpp
// 堆栈
//
// Created by lq on 2019/9/6.
// Copyright © 2019 Mr.liang. All rights reserved.
//
#ifndef stackcreat_hpp
#define stackcreat_hpp
//定义一个结构体用开表示链表的任一节点
//双向链表
struct node
{
int data;
node* next;
node* last;
};
//定义一个堆栈类
class stackcreat
{
private:
//友元函数定义的位置不影响其权限
node* head;
node* tail;
int length;//堆栈长度
//该友元函数执行将堆栈st复制到堆栈target的操作
friend stackcreat stackcopy(stackcreat target,const stackcreat & st);
public:
stackcreat();//构造函数,创建一个堆栈
~stackcreat();//析构函数
void empty();//清空一个堆栈
bool empty_check() const;//堆栈非空检查
int getsize() const;//堆栈元素数量统计
int & top() const;//得到最近入栈的元素
void pop();//执行一次出栈操作
void push(const int & number);//入栈number元素一次
void show_stack();//遍历输出堆栈元素
};
#endif /* stackcreat_hpp */
各个成员函数以及友元函数定义如下:
//
// stackcreat.cpp
// 堆栈
//
// Created by lq on 2019/9/6.
// Copyright © 2019 Mr.liang. All rights reserved.
//
#include "stackcreat.hpp"
#include <iostream>
using namespace std;
stackcreat::stackcreat()
{
head = new node;
tail = new node;
head->next = tail;
head->last = nullptr;
tail->last = head;
tail->next = nullptr;
length = 0;
}
stackcreat::~stackcreat()
{
cout<<"Bye!"<<endl;
}
bool stackcreat::empty_check() const
{
if(length == 0)
{
return true;
}
else
{
return false;
}
}
int stackcreat::getsize() const
{
return length;
}
int & stackcreat::top() const
{
node* p = head->next;
return p->data;
}
void stackcreat::pop()
{
node* p = new node;
p = head->next;
head->next = p->next;
p->next->last = head;
delete p;
length--;
}
void stackcreat::push(const int & number)
{
head->data = number;
head->last = new node;
node* p = head;
head = head->last;
head->next = p;
head->last = nullptr;
length++;
}
void stackcreat::show_stack()
{
node* temp = head->next;
while(temp != tail)
{
cout<<temp->data<<" ";
temp = temp->next;
}
cout<<endl;
}
stackcreat stackcopy(stackcreat target,const stackcreat & st)
{
node* temp = st.head->next;
while(temp != st.tail)
{
target.push(temp->data);
temp = temp->next;
cout<<"当前拷贝入栈元素:";
cout<<target.top()<<endl;
}
return target;
}
void stackcreat::empty()
{
node* temp = head;
while(temp != tail)
{
node* q = temp;
temp = temp->next;
delete q;
}
}
测试文件如下:
//
// main.cpp
// 堆栈
//
// Created by lq on 2019/9/6.
// Copyright © 2019 Mr.liang. All rights reserved.
//
#include <iostream>
#include "stackcreat.hpp"
using namespace std;
int main(int argc, const char * argv[]) {
// insert code here...
stackcreat stack,copy;
copy.push(100);
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);
stack.push(6);
stack.show_stack();
stack.pop();
stack.show_stack();
cout<<stack.top()<<endl;
stackcopy(copy,stack).show_stack();
stack.empty();
cout<<stack.empty_check()<<endl;
return 0;
}
运行结果如下:
6 5 4 3 2 1
5 4 3 2 1
5
当前拷贝入栈元素:5
当前拷贝入栈元素:4
当前拷贝入栈元素:3
当前拷贝入栈元素:2
当前拷贝入栈元素:1
1 2 3 4 5 100
Bye!
Bye!
0
Bye!
Bye!
Program ended with exit code: 0