Table of Contents:

C++类的定义和对象的创建详解

类只是一个模板(Template),编译后不占用内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据。只有在创建对象以后才会给成员变量分配内存,这个时候就可以赋值了。
使用 new 在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数。
用new要自己delete释放,用不好很危险,用好了功能强大,因为他可以赋值给全局的变量,一下子从局部变量变成全局变量,还能把对象作为函数返回值。
另外使用多态的时候也要用到new和指针
在一般开发里,常规定义(栈空间)与指针定义(堆空间)的区别不是很大。一般都是建议能不使用指针对象的时候不要使用。

C++类的成员变量和成员函数详解

在类体中定义的成员函数会自动成为内联函数,在类体外定义的不会。若内联函数变了,引用它的文件得重新编译,若不是内联的,只有函数签名改变了,引用此对象的c++文件才需要需重新编译

C++类成员的访问权限以及类的封装

public:类内、类外、继承可见
protected:类内、继承可见
private:类内可见

成员变量大都以m_开头,这是约定成俗的写法。以m_开头1. 既可以一眼看出这是成员变量,2. 又可以和成员函数中的形参名字区分开

//以 setname() 为例,如果将成员变量m_name的名字修改为name,那么 setname() 的形参就不能再叫name了,得换成诸如name1、_name这样没有明显含义的名字,否则name=name;这样的语句就是给形参name赋值,而不是给成员变量name赋值。
void Student::setname(char *name){
    m_name = name;
}

根据C++软件设计规范,实际项目开发中的成员变量以及只在类内部使用的成员函数都建议声明为 private,而只将允许通过对象调用的成员函数声明为 public。

C++对象的内存模型

对象所占用的存储空间的大小等于各成员变量所占用的存储空间的大小之和(如果不考虑成员变量对齐问题的话)。
对象的大小只受成员变量的影响,和成员函数没有关系。成员函数放在代码区。
和结构体非常类似,对象也会有内存对齐的问题

C++函数编译原理和成员函数的实现

C++和C语言的编译方式不同。C语言中的函数在编译时名字不变,或者只是简单的加一个下划线

而C++中的函数在编译时会根据它所在的1.命名空间、2.所属的类、3.参数列表(也叫参数签名)等信息进行重新命名,形成一个新的函数名。这个新的函数名只有编译器知道,对用户是不可见的。对函数重命名的过程叫做名字编码(Name Mangling)

Name Mangling 的算法是可逆的,既可以通过现有函数名计算出新函数名,也可以通过新函数名逆向推演出原有函数名。Name Mangling 可以确保新函数名的唯一性,只要函数所在的命名空间、所属的类、包含的参数列表等有一个不同,最后产生的新函数名也不同。
成员函数最终被编译成与对象无关的全局函数,如果函数体中没有成员变量,那问题就很简单,不用对函数做任何处理,直接调用即可。
C++规定,编译成员函数时要额外添加一个参数,把当前对象的指针传递进去,通过指针来访问成员变量。

void Demo::display(){
    cout<<a<<endl;
    cout<<b<<endl;
}
// 那么编译后的代码类似于:
void new_function_name(Demo * const this){   //this只能指向当前对象
    //通过指针this来访问a、b
    cout<<this->a<<endl;
    cout<<this->b<<endl;
}

这样通过传递对象指针就完成了成员函数和成员变量的关联。这与我们从表明上看到的刚好相反,通过对象调用成员函数时,不是通过对象找函数,而是通过函数找对象
最后需要提醒的是,Demo * const this中的 const 表示指针本身不能被修改,p 只能指向当前对象,而不是被指的对象不能修改。

C++构造函数详解

构造函数在实际开发中会大量使用,它往往用来做一些初始化工作,例如对成员变量赋值、预先打开文件等。
构造函数没有返回值,因为没有变量来接收返回值,即使有也毫无用处,这意味着:
 不管是声明还是定义,函数名前面都不能出现返回值类型,即使是 void 也不允许;
 函数体中不能有 return 语句。

一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,编译器都不再自动生成。
构造函数的重载:
如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。

最后需要注意的一点是,调用没有参数的构造函数也可以省略括号。在栈上创建对象可以写作Student stu()或Student stu,在堆上创建对象可以写作Student *pstu = new Student()或Student *pstu = new Student,它们都会调用构造函数 Student()
不同的是加()会进行值初始化,不加会默认初始化。

string *ps1=new string;  //默认初始化为空string
string *ps=new string(); //值初始化为空string
int *pi1=new int;        //默认初始化;*pi1的值未定义
int *pi2=new int();      //值初始化为0;*pi2的值为0

默认初始化和值初始化

默认初始化:对象可能产生未定义的值
1. 块作用域内不使用任何初始值定义一个非静态变量;
{ int *ptr; }
2. 类类型使用合成的默认构造函数;
3. 类类型的成员没有在构造函数初始值列表中显示地初始化;

值初始化:对象的值是确定(预设)的。
1. 数组初始化时初始值数量小于维度,剩下的元素会进行值初始化;

{ int array[10] = {1, 2, 3}; } 

2. 当我们不使用初始值定义一个局部静态变量;

{ static int n; } //n值初始化为0

3. 形如T()的表达式显示地请求值初始化;

{ 
     std::string *pia1 = new int[10](); //动态分配10个值初始化为0的int
     std::string *pia2 = new int[10]; //动态分配10个未初始化的int
}

C++构造函数初始化列表

注意,成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。
初始化 const 成员变量的唯一方法就是使用初始化列表,因为const初始化必须有其引用对象,只能初始化,不能复制。

C++析构函数详解

销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是析构函数。
析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数

它们与C语言中 malloc()、free() 最大的一个不同之处在于:用 new 分配内存时会调用构造函数,用 delete 释放内存时会调用析构函数。

析构函数的执行时机

1. 在所有函数之外创建的对象是全局对象,它和全局变量类似,位于内存分区中的全局数据区,程序在结束执行时会调用这些对象的析构函数。
2. 在函数内部创建的对象是局部对象,它和局部变量类似,位于栈区,函数执行结束时会调用这些对象的析构函数。
3. new 创建的对象位于堆区,通过 delete 删除时才会调用析构函数;如果没有 delete,析构函数就不会被执行。

C++对象数组(数组的每个元素都是对象)

CTest* pArray[2] = { new CTest(4), new CTest(1,2) };

C++成员对象和封闭类详解

成员对象的初始化

一个类的成员变量如果是另一个类的对象,就称之为成员对象。包含成员对象的类叫封闭类(enclosed class)
生成封闭类对象的语句一定要让编译器能够弄明白其成员对象是如何初始化的,否则就会编译错误

成员对象的消亡

封闭类对象生成时,先执行所有成员对象的构造函数,然后才执行封闭类自己的构造函数。成员对象构造函数的执行次序和成员对象在类定义中的次序一致.
当封闭类对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数,成员对象析构函数的执行次序和构造函数的执行次序相反,即先构造的后析构,这是 C++ 处理此类次序问题的一般规律。

C++ this指针详解

 this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。这个额外的参数,它是成员函数和成员变量关联的桥梁
 this 是 const 指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是不允许的
 this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的。
 只有当对象被创建后 this 才有意义,因此不能在 static 成员函数中使用。

C++ static静态成员变量详解

1) 一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量,都可以引用它。
2) static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。
static成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在类外初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。
3) 静态成员变量必须初始化,而且只能在类体外进行。例如:
int Student::m_total = 10;
初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化为 0。全局数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的,一般认为是垃圾值。
4) 静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。当通过对象名访问时,对于不同的对象,访问的是同一份内存。

C++ static静态成员函数详解

普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
静态成员函数的主要目的是访问静态成员,静态成员函数可以通过类来调用(一般都是这样做),也可以通过对象来调用.

C++ const成员变量和成员函数(常成员函数)

 初始化 const 成员变量只有一种方法,就是通过构造函数的初始化列表
 const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值,这种措施主要还是为了保护数据而设置的。比如:我们通常将 get 函数设置为const成员函数,这是一种保险的做法,同时也使得语义更加明显。
* 必须在成员函数的声明和定义处同时加上 const 关键字

C++ const对象(常对象)

const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的const成员(包括 const 成员变量和 const 成员函数)了。
语法:

const  class  object(params);
class const object(params);

当然你也可以定义 const 指针:

const class *p = new class(params);
class const *p = new class(params);

C++友元函数和友元类(C++ friend关键字)

友元函数

 借助友元(friend),可以使得其他类中的成员函数以及全局函数访问当前类的 private 成员。
 友元函数不同于类的成员函数(没有this指针),在友元函数中不能直接访问类的成员,必须要借助对象

1) 将非成员函数声明为友元函数。
2) 将其他类的成员函数声明为友元函数

友元类

不仅可以将一个函数声明为一个类的“朋友”,还可以将整个类声明为另一个类的“朋友”,这就是友元类。
友元类中的所有成员函数都是另外一个类的友元函数。

注意点

 友元的关系是单向的而不是双向的。如果声明了类 B 是类 A 的友元类,不等于类 A 是类 B 的友元类,类 A 中的成员函数不能访问类 B 中的 private 成员。
 友元的关系不能传递。如果类 B 是类 A 的友元类,类 C 是类 B 的友元类,不等于类 C 是类 A 的友元类。
* 除非有必要,一般不建议把整个类声明为友元类,而只将某些成员函数声明为友元函数,这样更安全一些。

类其实也是一种作用域

类其实也是一种作用域,每个类都会定义它自己的作用域。
 在类的作用域之外,普通的成员只能通过对象(可以是对象本身,也可以是对象指针或对象引用)来访问,
 静态成员既可以通过对象访问,又可以通过类访问,
 typedef 定义的类型只能通过来访问,类在这里就相当于作用域。
 函数的返回值类型出现在函数名之前,当成员函数定义在类的外部时,返回值类型中使用的名字都位于类的作用域之外

C++ class和struct到底有什么区别

 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
 class 继承默认是 private 继承,而 struct 继承默认是 public 继承
* class 可以用在模板定义中,而 struct 不能。想必这也在预料中,C++保留struct关键字主要是为了兼容C语言,但是在C语言中是不存在模板的,所以没有必要再模板中支持strcut声明模板形参。  template <struct T>这种声明是错的。

C++ 没有抛弃C语言中的struct关键字,其意义就在于给C语言程序开发人员有一个归属感,并且能让C++编译器兼容以前用C语言开发出来的项目。
在编写C++代码时,建议使用 class 来定义类,而使用 struct 来定义结构体

C++ string详解,C++字符串详解

* 转换为C风格的字符串,返回该字符串的 const 指针(const char*)

string path = "D:\\demo.txt";
FILE *fp = fopen(path.c_str(), "rt");
const char* c;
string s="1234";
c = s.c_str();
cout<<c<<endl; //输出:1234
s="abcd";
cout<<c<<endl; //输出:abcd

上面如果继续用c指针的话,导致的错误将是不可想象的。就如:1234变为abcd
其实上面的c = s.c_str(); 不是一个好习惯。既然c指针指向的内容容易失效,我们就应该把数据复制出来,这就要用到strcpy等函数(推荐)。

char* c=new char[20];
string s="1234";
strcpy(c,s.c_str());
cout<<c<<endl; //输出:1234
s="abcd";
cout<<c<<endl; //输出:1234

data():与c_str()类似,但是返回的数组不以空字符终止.

* 访问字符串中的字符

string s = "1234567890";
for(int i=0,len=s.length(); i<len; i++){
    cout<<s[i]<<" ";
}

* 字符串的拼接
使用+或+=运算符来直接拼接字符串,再也不需要使用C语言中的 strcat()、strcpy()、malloc() 等函数来拼接字符串了,再也不用担心空间不够会溢出了

* 插入字符串
insert() 函数的第一个参数有越界的可能,如果越界,则会产生运行时异常

//string& insert (size_t pos, const string& str);
string s1, s2, s3;
s1 = s2 = "1234567890";
s3 = "aaa";
s1.insert(5, s3)

* 删除字符串
如果不指明 len 的话,那么直接删除从 pos 到字符串结束处的所有字符(此时 len = str.length - pos)

//string& erase (size_t pos = 0, size_t len = npos);
string s1, s2, s3;
s1 = s2 = s3 = "1234567890";
s2.erase(5);
s3.erase(5, 3);

* 提取子字符串

// string substr (size_t pos = 0, size_t len = npos) const;
string s1 = "first second third";
string s2;
s2 = s1.substr(6, 6);

* 字符串查找

size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;

rfind() 和 find() 很类似,不同的是 find() 函数从第二个参数开始往后查找,而 rfind() 函数则最多查找到第二个参数处
find_first_of() 函数用于查找子字符串和字符串共同具有的字符在字符串中首次出现的位置

C++ string的内部究竟是什么样的?

在C语言中,有两种方式表示字符串:
 一种是用字符数组来容纳字符串,例如char str[10] = "abc",这样的字符串是可读写的;
 一种是使用字符串常量,例如char *str = "abc",这样的字符串只能读,不能写。
 
C++ string 对象知道自己在内存中的开始位置包含的字符序列以及字符序列长度;当内存空间不足时,string 还会自动调整,让内存空间增长到足以容纳下所有字符序列的大小。
 
C++ string 的这种做法,极大地减少了C语言编程中三种最常见且最具破坏性的错误:
 数组越界;
 通过未被初始化或者被赋以错误值的指针来访问数组元紊;
* 释放了数组所占内存,但是仍然保留了“悬空”指针。

C++ 标准没有定义 string 类的内存布局,各个编译器厂商可以提供不同的实现,但必须保证 string 的行为一致。采用这种做法是为了获得足够的灵活性

C++ 标准没有定义在哪种确切的情况下应该为 string 对象分配内存空间来存储字符序列。string 内存分配规则明确规定:允许但不要求以引用计数(reference counting)的方式实现。但无论是否采用引用计数,其语义都必须一致。

C++ 的这种做法和C语言不同,在C语言中,每个字符型数组都占据各自的物理存储区。在 C++ 中,独立的几个 string 对象可以占据也可以不占据各自特定的物理存储区,但是,如果采用引用计数避免了保存同一数据的拷贝副本,那么各个独立的对象(在处理上)必须看起来并表现得就像独占地拥有各自的存储区一样
只有当字符串被修改的时候才创建各自的拷贝,这种实现方式称为写时复制(copy-on-write)策略。当字符串只是作为值参数(value parameter)或在其他只读情形下使用,这种方法能够节省时间和空间。

不论一个库的实现是不是采用引用计数,它对 string 类的使用者来说都应该是透明的。遗憾的是,情况并不总是这样。在多线程程序中,几乎不可能安全地使用引用计数来实现

C++ STL 中的 std:string 类以字符 \0结尾么

无规定,但是我认为内部没有理由不以零结尾或不预留结尾零的位置
原因在于c_str()这个函数的调用
这个函数会返回c风格的字符串,是以零结尾的。如果内部不以零结尾或不预留结尾零的位置,那么这个函数的实现会比较低效率,因为意味着要重新分配更大的缓冲区来盛放数据。因此(或还有其他原因),主流实现都会以零结尾或预留结尾零的位置。