1、概述
C++学习过程记录。基于侯捷老师的视频。 整体思路:以此为框架,先学习大概内容,如有模糊点,在此框架对应目录出进行补充说明。
学习记录 学习记录 学习记录
2、C++代码的基本内容
2.1 基本组成
一个完整的C++程序一般包括声明部分(.h文件)+程序部分(.cpp文件)以及标准库部分。
【知识点】
- 自定义的头文件,使用双引号,见:#include “complex.h”
- 系统定义的头文件,使用尖括号,见:#include
2.2 防卫式声明
C++中头文件防卫式声明(Header Guard)是一种防止头文件被多次包含的机制,通常用于避免由于头文件的重复包含导致的编译错误。它的实现方式是在头文件的开头和结尾加上一些特定的代码,以确保头文件只被编译一次。
基本格式:
方式1:宏定义
#ifndef _FLLENAME_
#define _FILENAME_
// 头文件内容定义
#endif
方式2:编译器指令
#pragma once
在这两种声明方式中:
- #ifndef 依赖于宏定义名,当宏已经定义时,#endif之前的代码就会被忽略,但是这里需要注意宏命名重名的问题;
- #pragma once 只能保证同一个文件不会被编译多次,但是当两个不同的文件内容相同时,仍然会出错。而且这是微软提供的编译器命令,当代码需要跨平台时,需要使用宏定义方式。
详解:https://www.cnblogs.com/zaiyewujiang/p/17347098.html
2.3 头文件的布局
2.3.1 前置声明
- 尽量避免前置声明那些定义在其他项目中的实体.
- 函数:总是使用 #include.
- 类模板:优先使用 #include.
2.3.2 class的声明
1)定义声明
/**
*以上部分定义类的头部
**/
class complex
{
public:
complex(double x, double y):re(x),im(y){};
complex& operator += (const complex&);
double real()const{
return re;
};
double imag() const{
return im;
}
private:
double re,im;
friend complex& _doapl(complex*, const complex&);
};
2)类模板的写法
template <typename T>class complex
{
public:
complex(T x, T y) : re(x), im(y){};
complex &operator+=(const complex &);
T real() const
{
return re;
};
double imag() const
{
return im;
}
private:
T re, im;
friend complex &_doapl(complex *, const complex &);
};
/****
*使用方式:
complex<double>c1(2.1, 3.4);
complex<int>c2(3,4)
*/
实例:函数模板的调用:
#include <iostream>
#include <stdio.h>
using namespace std;
template <typename T1,typename T2> //模板函数声明与定义
T2 test(T1 tmp, T2 tmp1) {
T2 tmp2 = tmp + tmp1;
return tmp2;
}
int main(void) {
cout << "test(5,'A')=" << test<int,char>(5, 'A') << endl; //<int,char>显式的指明模板的类型
cout << "test('D',9)=" << test<char,int>('D',9) << endl; //<int,char>显式的指明模板的类型
return 0;
}
3、构造函数的定义
以一个函数为例展开:
C中类的构造函数写法有两种,一种是使用初始化列表,一种是在代码块中赋值。
推荐使用初始化列表的方式,列表初始化效率要高于在代码块中直接赋值的方式。
代码1:
class Man{
public:
// 默认的构造函数
Man(){
cout << "call Man::Man()" << endl;
}
// 初始化列表的方式 **关注点**
Man(string n, int a):name(n),age(a) {
cout << "call Man::Man(string, int)" << endl;
}
// 拷贝构造函数
Man(const Man& a) {
cout << "call Man::Man(const Man&)" << endl;
}
// 重载了对象赋值操作
Man& operator=(const Man& a) {
name = a.name;
age = a.age;
cout << "call Man::operator=(const Man& a)" << endl;
return *this;
}
private:
string name;
int age;
};
代码2:
class ChineseMan {
public:
// 带参数的构造函数,用代码块赋值的方式来初始化 **关注点**
ChineseMan(Man a, string i) {
man = a;
id = i;
}
private:
string id;
Man man;
};
代码3:
class ChineseMan {
public:
// 带参数的构造函数,用初始化列表的方式来初始化 **关注点**
ChineseMan(Man a, string i):man(a), id(i) {}
private:
string id;
Man man;
};
main函数测试:
int main()
{
Man vincent("vincent", 26);
cout << "-----------" << endl;
ChineseMan vincent_CN(vincent, "001");
return 0;
}
以代码2的方式运行:代码块构造
call Man::Man(string, int)
-----------
call Man::Man(const Man&)
call Man::Man()
call Man::operator=(const Man& a)
创建vincent_CN的过程可以分为三步:
- 首先调用了Man的拷贝构造函数,因为需要把实参vincent拷贝赋值给形参a;
- 然后又调用了Man的默认构造函数,因为需要创建vincent_CN对象的man成员;
- 第三步调用赋值运算符函数,把对象a赋值给对象man。至此完成vincent_CN的构造过程;
以代码3的方式运行:初始化列表的方式
- 第一步调用拷贝构造函数,是因为传递参数需要把实参vincent传递给形参a。
- 第二步再次调用拷贝构造函数,是用形参a来构造数据成员man。
- 因此用初始化列表构造对象,调用了两次拷贝构造函数。
class ChineseMan {
public:
ChineseMan(Man a, string i):man(a), id(i) {}
private:
string id;
Man man;
};
结论: 对比两种方法的执行结果,可以看出来:用初始化列表构造对象,在实参传给形参之后,直接就调用拷贝构造函数用形参来构造数据成员了,不需要经历先构造再赋值的操作。因此效率上确实要比代码块初始化高一些。