C++踩坑

在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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值