C++知识点总结

malloc与new的区别:

1.new是C++中的操作符,malloc是C中的一个函数
2.new不止是分配内存,而且会调用类的构造函数,同时delete会调用类的析构函数,而malloc则只分配内存,不会进行初始化类成员的工作,同样free也不会调用析构函数
3.内存泄露对于malloc或new都可以检查出来的,区别在于new可以指明是哪个文件的哪一行,而malloc没有这些信息
4.new和malloc效率比较:malloc的效率高一点,new可以认为是malloc加构造函数的执行,delete是free加析构函数的执行
5.new出来的指针是直接带类型信息的,malloc返回的都是void指针
6.new/delete是运算符,malloc/free是函数。运算符只能重载不能自定义;函数的名字随便起,只要是个标识符就行了,但运算符不行,比如,你无法仿照其它语言的特点,自己定义一个乘方运算符“**”,任何函数都可以重载或者覆盖,但通常你不能改变运算符作用于内置类型的行为,比如,你不能通过重载“operator+”,让3+2产生出6来
7.malloc与free是C/C++语言的标准库函数,new/delete是C++的运算符,它们都可用于申请动态内存和释放内存
8.为什么C++中要存在new/delete,只用malloc/free不就行了吗?

对于非内部数据类型(如int,char,float等属于内部数据类型,struct,class等自定义的类型则是非内部类型)光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此,C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete

memcpy

c和c++使用的内存拷贝函数

函数原型:
void memcpy(void dest,const void *src,size_t n);

功能:
从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

所需头文件:
c语言中使用#include c++中使用#include 和#include 都可以

返回值:
函数返回指向dest的指针

说明:
1.source和destin所指的内存区域可以重叠,但是如果source和destin所指的内存区域重叠,那么这个函数并不能确保source所在重叠区域在拷贝之前被覆盖。
2.如果目标数组destin本身已有数据,执行memcpy()后,将覆盖原有数据(最多覆盖n)。如果要追加数据,则每次执行memcpy后,要将目标数组地址增加到你要追加数据的地址。
注意:source和destin都不一定是数组,任意的可读写的空间均可

Linux中函数的实现:

void *memcpy(void *dest,const void *src,size_t count)
{
    assert(dest!=NULL&&src!=NULL);
    char *tmp=dest;
    const char *s=src;
    while(count--)
    {
        *tmp++=*s++;
    }
    return dest;
}       

example 1:将s中的字符串复制到字符数组d中

//memcpy.c
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
    char *s="Golden Global View";
    char d[20];
    memcpy(d,s,(srtlen(s)+1));
    cout<<d<<endl;
    system("pause");
    return 0;
}

输出结果: Golden Global View

strcpy

C语言标准库函数strcpy,把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间。

C语言标准库函数
原型声明:extern char strcpy(char dest,const char *src);
头文件:#include
功能:把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间
说明:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串,返回指向dest的指针

题目:
已知strcpy函数的原型是char strcpy(char dest,const char src);
1.不调用库函数,实现strcpy函数
2.解释为什么要返回char

1.strcpy的实现代码

char *strcpy(char *strDest,const char *strSrc)
{
    char *strDestCopy=strDest;
    if((strDest==NULL)||(strSrc==NULL))
        throw "Invalid arguments(s)";
    while((*strDestCopy++==*strSrc++)!='\0');
    return strDest;
}

在上面的语句中,循环语句while((strDestCopy++==strSrc++)!=’\0’);较难理解,可以把这句理解为以下操作。
第一种:

while(1)
{
    char temp;
    temp=*strDestCopy=*strSrc;
    strDestCopy++;
    strSrc++;
    if('\0'==temp)
        break;
}                          

第二种:

while(*strSrc!='\0')
{
    *strDestCopy=*strSrc;
    strDestCopy++;
    strSrc++;
}
*strDestCopy=*strSrc;

strcpy与memcpy的区别

前面两篇文章分别介绍了memcpy函数和strcpy函数,两者都可以用来进行字符串的复制。下面我们就看看二者之间的区别。

  1. 复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整形、结构体、类等。
  2. 复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符“\0”才结束,所以容易溢出。memcpy则是根据其第三个参数决定复制的长度。
  3. 用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

const

一些成员函数改变对象,一些成员函数不改变对象。
例如:

int Point::GetY()
{
   return yVal;
}

这个函数被调用时,不改变Point对象,而下面的函数改变Point对象:

void Point::setPoint(int x,int y)
{
   xVal=x;
   yVal=y;
}

为了使成员函数的意义更加清楚,我们可在不改变对象的成员函数的函数原型中加上const说明:

class Point
{
 public:
   int GetX() const;
   int GetY() const;
   void setPoint(int,int);
  private:
    int xVal,yVal;
};

const成员函数应该在函数原型说明和函数定义中都增加const限定:

int Point::GetY() const
{
   return yVal;
}
class Set
{
 public:
   Set(void){card=0;}
   bool Member(const int) const;
   void AddElem(const int);
   //...
};
bool Set::Member(const int elem) const
{
  //...
}

非常量成员函数不能被常量成员对象调用,因为他可能企图修改常量的数据成员:

const Set s;
s.AddElem(10);  //非法,AddElem不是常量成员函数
s.Member(10);   //正确

但构造函数和析构函数对这个规则例外,它们从不定义为常量成员,但可被常量对象调用(被自动调用)。它们也能给常量的数据成员赋值,除非数据成员本身是常量。

为什么需要const成员函数?
我们定义的类的成员函数中,常常有一些成员函数不改变类的数据成员,也就是说,这些函数是“只读”函数,而有一些函数要修改类数据成员的值。如果把不改变数据成员的函数都加上const关键字进行标识,显然,可提高程序的可读性。其实,还能提高程序的可靠性,已定义成const的成员函数,一旦企图修改数据成员的值,则编译器按错误处理。
为了确保const对象的数据成员不会被改变,在C++中,const对象只能调用const成员函数。如果一个成员函数实际上没有对数据成员作任何形式的修改,但是它没有被const关键字限定,也不能被常量对象调用。
const成员函数的使用
const成员函数表示该成员函数只能读类数据成员,而不能修改类成员数据。定义const成员函数时,把const关键字放在函数的参数表和函数体之间。有人可能会问:为什么不将const放在函数声明前呢?因为这样做意味着函数的返回值是常量,意义完全不同。

对象.成员函数
对象 成员函数 对/错

  1. const const 对
  2. const no-const 错
  3. no-const const 对
  4. no-const no-const 对

成员函数调用成员函数
成员函数 成员函数 对/错

  1. const const 对
  2. const no-const 错
  3. no-const const 对
  4. no-const no-const 对

static成员

在C++中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。

静态成员的定义或声明要加个关键字static。静态成员可以通过双冒号来使用即<类名>::<静态成员名>。
第一个例子,通过类名调用静态成员函数和非静态成员函数

class Point
{
 public:
   void init() {}
   static void output() {}
};
void main()
{
  Point::init();
  Point::output;
}

编译出错:error C2352: ‘Point::init’ : illegal call of non-static member function
结论1:不能通过类名来调用类的非静态成员函数

第二个例子,通过类的对象调用静态成员函数和非静态成员函数,将上例的main()改为:

void main()
{
  Point pt;
  pt.init();
  pt.output();
}

编译通过。
结论2:类的对象可以使用静态成员函数和非静态成员函数

第三个例子,在类的静态成员函数中使用类的非静态成员

#include <stdio.h>
class Point
{
 public:
   void init() {}
   static void output()
   {
     printf("%d\n",m_x);
   }
 private:
   int m_x;
};
void main()
{
  Point pt;
  pt.output();
}

编译出错:error C2597: illegal reference to data member ‘Point::m_x’ in a static member function
因为静态成员函数属于整个类,在类实例化对象之前就已经分配空间了,而类的非静态成员必须在类实例化对象之后才有内存空间,所以这个调用就出错了,就好比没有声明一个变量却提前使用它一样。
结论3:静态成员函数中不能引用非静态成员

第四个例子,在类的非静态成员函数中使用类的静态成员

class Point  
{  
 public:   
   void init()  
   {    
      output();  
   }  
   static void output() {}  
};  
void main()  
{  
  Point pt;  
  pt.output();  
} 

编译通过
结论4:类的非静态成员函数可以调用用静态成员函数,但反之不能。

第五个例子,使用类的静态成员变量

#include <stdio.h>  
class Point  
{  
 public:   
  Point()  
  {    
    m_nPointCount++;  
  }  
 ~Point()  
 {  
    m_nPointCount--;  
 }  
 static void output()  
 {  
    printf("%d\n", m_nPointCount);  
 }  
private:  
 static int m_nPointCount;  
};  
void main()  
{  
  Point pt;  
  pt.output();  
}  

按Ctrl+F7编译无错误,按F7生成EXE程序时报链接错误:
error LNK2001: unresolved external symbol “private: static int Point::m_nPointCount” (?m_nPointCount@Point@@0HA)
这是因为类的静态成员变量在使用前必须先初始化。
在main()函数前加上int Point::m_nPointCount = 0;
再编译链接无错误,运行程序将输出1。
结论5:类的静态成员变量必须先初始化再使用

结合上面的五个例子,对类的静态成员变量和成员函数做个总结:

  1. 静态成员函数中不能调用非静态成员
  2. 非静态成员函数中可以调用静态成员,因为静态成员属于类本身,在类的对象产生之前就已经存在了,所以在非静态成员函数中是可以调用静态成员的
  3. 静态成员变量使用前必须先初始化,否则会在linker时出错

再给一个利用类的静态成员变量和函数的例子以加深理解,这个例子建立一个学生类,每个学生类的对象将组成一个双向链表,用一个静态成员变量记录这个双向链表的表头,一个静态成员函数输出这个双向链表。

#include <stdio.h>
#include <string.h>
const int MAX_NAME_SIZE = 30;
class Student
{
 public:
   Student(char *pszName);
   ~Student();
 public:
   static void PrintfAllStudents();
 private:
   char m_name[MAX_NAME_SIZE];
   Student *next;
   Student *prev;
   static Student *m_head;
};
Student::Student(char *pszName)
{
  strcpy(this->m_name,pszName);
  //建立双向链表,新数据从链表头部插入
  this->next=m_head;
  this->prev=NULL;
  if(m_head!=NULL)
  {
    m_head->prev=this;
  }
  m_head=this;
}
Student::~Student()      //析构过程就是节点的脱离过程
{
  if(this==m_head)
  {
    m_head=this->next;
  }
  else
  {
    this->prev->next=this->next;
    this->next->prev=this->prev;
  }
}
void Student::PrintfAllStudents()
{
  for(Student *p=m_head;p!=NULL;p=p->next)
  {
    printf("%s\n",p->m_name);
  }
}
Student* Student::m_head=NULL;
void main()
{
  Student studentA("AAA");
  Student studentB("BBB");
  Student studentC("CCC"); 
  Student studentD("DDD");
  Student student("MoreFree");
  Student::PrintfAllStudents();
}

程序将输出:
图片
当然在本例还可以增加个静态成员变量来表示链表中学生个数,如果读者有兴趣,就将这个作为小练习吧。