1. 内联函数
- 声明时使用关键字inline
- 编译时在调用处用函数体进行替换,节省了参数传递,控制转移等开销。
- 注意:
1. 内联函数体内不能有循环语句和switch语句
2. 内联函数的定义必须出现在第一次调用之前
3. 对内联函数不能进行异常接口声明
2. 带默认参数值的函数
- 有默认参数的形参必须在参数列表的最后,即默认参数值的右边不能有无默认值的参数。
- 调用实参与形参的结合顺序时从左向右。
1. int add(int x, int y = 5, int z = 6);//正确
2. int add(int x = 1, int y = 5, int z);//错误
3. int add(int x = 1, int y, int z = 6);//错误
3. 函数重载
- C++允许功能相近的函数在相同的作用域内以相同函数名声明,从而形成重载。方便使用,便于记忆。
- 注意事项
1. 重载函数的形参必须不同:个数不同或类型不同。
2. 编译程序将根据实参和形参的类型及个数的最佳匹配来选择调用哪一个函数。
4. 委托构造函数
- 一个构造函数委托另外一个构造函数完成初始化功能
- Clock类的两个构造函数
1. Clock(int newH, int newM, int newS):hour(newH), minute(newM), second(newS) { } //构造函数
2. Clock():Clock(0, 0, 0) { } //默认构造函数 (委托构造函数)
3. 2等于Clock():hour(0), minute(0), second(0) { } - 优势:
1. 简化代码,重复的或相似的内容写一次即可;
2. 保持代码实现的一致性:修改构造函数的时候;
5. 复制构造函数(拷贝构造函数)
- 复制构造函数是一种特殊的构造函数,其形参为本类的对象引用。作用是用一个已经存在的对象去初始化同类型的新对象。
- 例:
Class Clock {
public:
Clock(int newH, int newM, int newS):hour(newH), minute(newM), second(newS) { }
Clock(const Clock &c);
private:
int hour, minute, second;
};
//成员函数实现
Clock::clock(const Clock &c){
hour = c.hour;
minute = c.minute;
second = c.second;
cout << "calling the copy constructor" << endl;
}
- 三种调用复制构造函数的情况:
- 定义一个对象时,以本类另一个对象作为初始值,发生复制构造;
- 如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
- 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主调函数,此时发生复制构造。
class Clock {
public:
Clock(int newH, int newM, int newS);
Clock();
Clock(const Clock &c);
void showtime();
void showtime() const;
static void show_s_time();
~Clock(){}
friend void show(const Clock &c);
private:
int minute, second;
const int hour;
static int time; //声明
static const int b;
};
//静态成员定义及初始化
int Clock::time = 12;
int Clock::b = 10;
//
//成员函数实现
1. 构造函数
Clock::Clock(int newH, int newM, int newS):hour(newH), minute(newM), second(newS) {
}
2. 默认构造函数
Clock::Clock():Clock(0, 0, 0) {
}
3. 复制构造函数
Clock::Clock(const Clock &c):hour(c.hour){
//hour = c.hour; const 数据成员必须在初始化列表进行定义(赋值);
minute = c.minute;
second = c.second;
cout << "calling the copy constructor" << endl;
}
4. showtime()
inline void Clock::showtime(){
cout << hour << ":" << minute << ":" << second << endl;
}
5. showtime()重载
inline void Clock::showtime() const{
cout << hour << ":" << minute << ":" << second << endl;
}
6. show_s_time()
inline void Clock::show_s_time(){
cout << "time: " << time << endl;
}
//外部函数fun1 形参为Clock类对象
void fun1(Clock c){
c.showtime();
}
//外部函数fun2 返回值为Clock类对象
Clock fun2(){
Clock a;
return a;
}
//友元函数
void show(const Clock &c){
cout << c.hour << " " << c.minute << " " << c.second << endl;
}
int main(){
Clock a;
Clock b(a); //情况1
b.showtime();
fun1(b); //情况2
b = fun2(); //情况3 ;若将hour声明为const,那么该句有误,原因是hour为const型,不可更改,即构造时就进行定义,不能进行二次更改;
b.showtime();
Clock::show_s_time();
Clock d(3, 4, 20);
show(d);
return 0;
}
6. 数据共享与保护
-
对象的生存期
1. static 修饰的变量只在程序编译时赋值,并且只初始化赋值一次;若不初始化,则为0(int);
2. static 具有隔离和隐藏的作用,可以修饰变量,函数,类等; -
类的静态成员
1. 用关键字static声明;
2. 为该类的所有对象共享,静态数据成员具有静态生存期;
3. 静态成员变量必须在类内声明,类外定义和初始化(原因是属于整个类,如果在类内初始化则在每个对象构建时出现重复初始化的情况),用(::)来指明所属的类;
4. 静态成员函数只能访问静态变量,不能访问普通成员变量,原因是:静态成员函数属于整个类,不具有this指针,而非静态成员变量在对象构建时才会分配内存,因此静态成员函数不能访问; -
共享数据的保护
1. 对于即需共享,又需要防止改变的数据应声明为常类型(用const进行修饰);
2. 对于不改变对象状态的成员函数应声明为常函数; -
常类型
1. 常对象(类):
a. 必须进行初始化,由于const类型不能被更改,所以必须进行初始化;
b. 形式:const 类名 对象名;
c. 常对象只能调用常函数成员,不能调用非常函数成员;
d. 类对象能调用函数成员和常函数成员
2. 常成员(类):
a.用const修饰的成员:常数据成员、常函数成员;
b. 类函数成员能访问常数据成员和非常数据成员;
c. 类常函数成员能访问常数据成员和非常数据成员;
3. 常引用:
a. 被引用的对象不能被更新;
b. 例如:复制构造函数;
4. 常数组:
a. 数组元素不能被更新;
5. 常指针 : 详见7.2.1; -
多文件结构和预编译命令
1. 外部变量:
a. 如果一个变量除了在定义它的源文件中可以使用外,还能被其它文件使用,那么就称这个变量是外部变量。
b. 文件作用域中定义的变量,默认情况下都是外部变量,但在其它文件中如果需要使用这一变量,需要用extern关键字加以声明。
2. 外部函数
a. 在所有类之外声明的函数(也就是非成员函数),都是具有文件作用域的。
b. 这样的函数都可以在不同的编译单元中被调用,只要在调用之前进行引用性声明(即声明函数原型)即可。也可以在声明函数原型或定义函数时用extern修饰,其效果与不加修饰的默认状态是一样的。
7. 数组、指针与字符串
- 数组的存储与初始化
1. 数组的名字是首元素的地址;
2. 数组名是一个常量,不能被赋值;
3. 一维数组的初始化:
a. 列出全部元素的初始值:
例如:static int a[10]={0,1,2,3,4,5,6,7,8,9};
b. 可以只给一部分元素赋初值
例如:static int a[10]={0,1,2,3,4};
c. 在对全部数组元素赋初值时,可以不指定数组长度
例如:static int a[]={0,1,2,3,4,5,6,7,8,9}
4. 二维数组的初始化
a. 将所有初值写在一个{}内,按顺序初始化
例如:static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
b. 分行列出二维数组元素的初值
例如:static int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
c. 可以只对部分元素初始化
例如:static int a[3][4]={{1},{0,6},{0,0,11}};
c. 列出全部初始值时,第1维下标个数可以省略
例如:static int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
或:static int a[][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
5. (4)注:
a. 如果不作任何初始化,局部作用域的非静态数组中会存在垃圾数据,static数组中的数据默认初始化为0;
b. 如果只对部分元素初始化,剩下的未显式初始化的元素,将自动被初始化为零; - 指针与函数
1. 指针的定义和运算
a. const 指针:
b. 指针类型的常量
2. 以指针作为函数参数(区别于引用):最小化原则(形参权限够用就行)
a. 当函数的形参为指针时:形参只是获得了实参的地址值,当改变形参的指向时,实参的指向并不改变
b. 当函数的形参为指针引用时:形参不仅仅获得了实参的地址值,还和实参地址绑定,改变形参的指向就能改变实参的指向。
c. const指针作形参:对传入的实参只读,不能改变其值;
void print(const int* p){
cout << *p << endl;
}
d. 指向常量的指针作形参:相当于引用作形参;
void print(int* const p){
*p = 3; //可以改变传入实参的值,但不能改变指向地址;
cout << *p << endl;
}
- 指针类型的函数:
a. 不能将非静态局部地址用作函数的返回值,存在安全隐患;
int* function(){
int local = 0; //非静态局部变量的作用域和寿命仅限于函数体内;
return &local;
}//函数运行结束后,变量local被释放;
b. 返回的指针要确保在主调函数中是有效、合法的地址;例如:在主函数中定义数组;
c. 在子函数中通过动态内存分配new操作取得的内存地址返回给主函数是合法有效的,但是内存分配和释放不在同一级别,要注意不能忘记释放,避免内存泄漏;
- 指向函数的指针:
a. 功能:实现函数回调;
b. 不用考虑被调用者是谁,可以灵活的使用不同的函数;
例:
int computer(int a, int b, int(*fun)(int, int)){
return (fun(a, b));
}
int sum(int a, int b){
return a + b;
}
int min(int a, int b){
return ((a < b)? a : b);
}
int max(int a, int b){
return ((a > b)? a : b);
}
compute(a, b, &sum);
compute(a, b, &min);
compute(a, b, &max);
8 继承与派生
- 公有继承
1. 继承的访问控制
a. 基类的public和protected成员:访问属性在派生类中保持不变;
b. 基类的private成员:不可直接访问。
2. 访问权限
a. 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
b. 通过派生类的对象:只能访问public成员。 - 私有继承
1. 继承的访问控制
a. 基类的public和protected成员:都以private身份出现在派生类中;
b. 基类的private成员:不可直接访问。
2. 访问权限
a. 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
b. 通过派生类的对象:不能直接访问从基类继承的任何成员。 - 保护继承
1. 继承的访问控制
a. 基类的public和protected成员:都以protected身份出现在派生类中;
b. 基类的private成员:不可直接访问。
2. 访问权限
a. 派生类中的成员函数:可以直接访问基类中的public和protected成员(以成员名::函数名的方式),但不能直接访问基类的private成员;
b. 通过派生类的对象:不能直接访问从基类继承的任何成员。