1.命名空间
为了解决合作开发时的命名冲突问题。
namespace Li{ //小李的变量定义
FILE* fp = NULL;
}
namespace Han{ //小韩的变量定义
FILE* fp = NULL;
}
//使用1
Li::fp = fopen("one.txt", "r"); //使用小李定义的变量 fp
Han::fp = fopen("two.txt", "rb+"); //使用小韩定义的变量 fp
//使用2
using Li::fp;
fp = fopen("one.txt", "r"); //使用小李定义的变量 fp
Han :: fp = fopen("two.txt", "rb+"); //使用小韩定义的变量 fp
2. new和delete
1)malloc 和 free
int *p = (int *) malloc(sizeof(int) * 10); //分配10个
free (p);
2)new 和 delete
int *p = new int;
delete p;
int *p = new int[10]; //分配10个
delete[] p;
用new[]分配的内存需要用delete[]释放,一一对应;new和delete可以自动调用构造函数和析构函数。
3. inline 内联函数
如果函数只有一两条语句,为了消除函数调用的时空开销,内联函数在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。
使用内联函数的缺点也是非常明显的,编译后的程序会存在多份相同的函数拷贝,如果被声明为内联函数的函数体非常大,那么编译后的程序体积也将会变得很大,所以再次强调,一般只将那些短小的、频繁调用的函数声明为内联函数。
对函数作 inline 声明只是程序员对编译器提出的一个建议,而不是强制性的,并非一经指定为 inline 编译器就必须这样做。编译器有自己的判断能力,它会根据具体情况决定是否这样做。
4. 重载
C++ 允许多个函数拥有相同的名字,只要它们的参数列表不同就可以,包括参数的类型、参数的个数和参数的顺序,只要有一个不同就叫做参数列表不同,这就是函数的重载(Function Overloading)。借助重载,一个函数名可以有多种用途。
函数的重载的规则:
- 函数名称必须相同。
- 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
- 函数的返回类型可以相同也可以不相同。
- 仅仅返回类型不同不足以成为函数的重载。
编译器是如何做到重载的?代码在编译时会根据参数列表对函数进行重命名,例如void Swap(int a, int b)
会被重命名为_Swap_int_int
,void Swap(float x, float y)
会被重命名为_Swap_float_float
,这就叫重载决议。从这个角度讲,函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。
5. 类与对象
类只是一个模板(Template),编译后不占用内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据。只有在创建对象以后才会给成员变量分配内存,这个时候就可以赋值了。
new创建的对象在堆上分配内存,也就是说,使用 new 在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数。
通过对象名字访问成员使用点号.
,通过对象指针访问成员使用箭头->
,这和结构体非常类似。
6. 成员变量和成员函数
但当成员函数定义在类外时,就必须在函数名前面加上类名予以限定。::
被称为域解析符(也称作用域运算符或作用域限定符),用来连接类名和函数名,指明当前函数属于哪个类。
类体中定义成员函数和类外定义成员函数有区别:在类体中定义的成员函数会自动成为内联函数,在类体外定义的不会。内联函数一般不是我们所期望的,它会将函数调用处用函数体替代,所以建议在类体内部对成员函数作声明,而在类体外部进行定义,这是一种良好的编程习惯,实际开发中大家也是这样做的。
7. 构造函数
构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。
8. 初始化列表
采用初始化列表对成员变量进行初始化:
Student::Student(char *name, int age, float score):
m_name(name), m_age(age), m_score(score){
//TODO:
}
- 使用构造函数初始化列表并没有效率上的优势,仅仅是书写方便
- 初始化列表可以用于全部成员变量,也可以只用于部分成员变量,还可用于基类的构造(在派生类的构造中)
- 成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关
- 初始化 const 成员变量的唯一方法就是使用初始化列表
9. 析构函数
析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。
在所有函数之外创建的对象是全局对象,它和全局变量类似,位于内存分区中的全局数据区,程序在结束执行时会调用这些对象的析构函数;在函数内部创建的对象是局部对象,它和局部变量类似,位于栈区,函数执行结束时会调用这些对象的析构函数;new 创建的对象位于堆区,通过 delete 删除时才会调用析构函数;如果没有 delete,析构函数就不会被执行。
10. this指针
this 是 C++中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。
成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。
- this 是 const 指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是不允许的。
- this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的。
- 只有当对象被创建后 this 才有意义,因此不能在 static 成员函数中使用
11. static静态成员变量
- 可以使用静态成员变量来实现多个对象共享数据的目标
- static 成员变量必须在类声明的外部初始化
- static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用
- static 成员变量和普通的 static 变量类似,都在内存分区中的全局数据区分配内存
12. static静态成员函数
- 普通成员函数可以访问所有成员(包括成员变量和成员函数),静态成员函数只能访问静态成员
- 静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数
- 静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)
13. const成员变量和成员函数
const 成员变量的用法和普通 const 变量的用法相似,初始化 const 成员变量只有一种方法,就是通过构造函数的初始化列表。
const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值。需要强调的是,必须在成员函数的声明和定义处同时加上 const 关键字。
- 函数开头的 const 用来修饰函数的返回值,表示返回值是 const 类型,也就是不能被修改,例如
const char * getname()
- 函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修改成员变量的值,例如
char * getname() const
14. const常对象
一旦将对象定义为常对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数
const class object(params); // 定义常对象
const class *p = new class(params); // 定义常对象指针
15. 友元函数和友元类
友元函数可以访问当前类中的所有成员,包括 public、protected、private 属性的。友元函数不同于类的成员函数,在友元函数中不能直接访问类的成员,必须要借助对象(可以直接传递对象,也可以传递对象指针或对象引用)。一般情况下,类必须在正式声明之后才能使用;但是某些情况下,只要做好提前声明,也可以先使用
class Student{
//...
public:
void show();
//...
};
class Address{
public:
friend void Student::show(); //这里的show方法可以访问Address类中的所有成员
};
友元类中的所有成员函数都是另外一个类的友元函数。除非有必要,一般不建议把整个类声明为友元类,而只将某些成员函数声明为友元函数,这样更安全一些。
class Student{
//...
};
class Address{
public:
friend class Student; //友元类,因此Student类的对象可以访问Address类中所有成员
};
- 友元的关系是单向的而不是双向的
- 友元的关系不能传递
16. class 和 struct
在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。C++中的 struct 和 class 基本是通用的,唯有几个细节不同:
- class中成员默认是private属性,struct中成员默认是public属性
- class 继承默认是 private 继承,而 struct 继承默认是 public 继承
- class 可以使用模板,而 struct 不能
17. String字符串
#include<iostream>
#include<string>
int main(){
std::string s1; //默认值“”,空字符串
std::string s2 = "c plus plus"; //string 的结尾没有结束标志'\0'
std::string s3 (5, 's');
int len = s2.length(); //string类型字符串长度
//string转C语言字符串,c_str()返回const char*
string path = "D:\\demo.txt";
FILE *fp = fopen(path.c_str(), "rt");
//访问字符串中的字符
//for循环
//通过下标,s2[2] = '_';
//字符串拼接,使用 '+', '+='运算符
//1.插入字符串,pos表示要插入的位置
string& insert (size_t pos, const string& str);
//2.删除字符串,如果 pos 越界,会抛出异常;len 越界,会删除从 pos 到字符串结尾处的所有字符
string& erase (size_t pos = 0, size_t len = npos);
//3.提取字符串,如果 pos 越界,会抛出异常;len 越界,会提取从 pos 到字符串结尾处的所有字符
string substr (size_t pos = 0, size_t len = npos) const;
//4.字符串查找,
//find()从第二个参数开始往后查找,如果没有查找到子字符串,那么会返回一个无穷大值 4294967295
size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;
//rfind() 函数则最多查找到第二个参数处
//find_first_of() 函数用于查找子字符串和字符串共同具有的字符在字符串中首次出现的位置
string s1 = "first second second third";
string s2 = "asecond";
int index = s1.find_first_of(s2);
return 0;
}
18. 引用
- 引用可以看做是数据的一个别名(没有发生拷贝),通过这个别名和原来的名字都能够找到这份数据
- 引用必须在定义的同时初始化,并且以后也要从一而终,不能再引用其它数据,这有点类似于常量(const 变量)
- 引用在定义时需要添加
&
,在使用时不能添加&
,使用时添加&
表示取地址 - 将引用作为函数返回值时应该注意一个小问题,就是不能返回局部数据,因为当函数调用完成后局部数据就会被销毁
参考
1.http://c.biancheng.net/cplus/