实用链接
C++语言中文网
实例用法查看网址
c++在线编译器
google代码风格规范
0.0 编译运行
.c
是C语言代码,.cpp
是C++代码
g++
是执行c++文件的命令;gcc
是执行c文件的命令- bin文件运行
chmod +x filename.bin
./filename.bin ....
单个程序编辑调试
-
step 1: 写一个
xx.cc
或者xx.cpp
的文件 -
step 2:编译文件
g++ xx.cpp
(编译单个文件),g++ xx1.cpp xx2.cpp
(编译多个文件) -
step 3:生成对应的可执行文件
g++ xx1.cpp -o demo
,demo
是可执行文件的名字
如果c++文件有错误是不会生成对应的可执行文件的 -
step 3:
./demo
运行文件(这样操作,才能看到里边的printf等信息,否则是没有的) -
linux环境下,‘ctrl+d’是输入结束的字符
库文件编译调试
- step 1: 一般新建一个build文件夹(其他也可以),用于存放编译过程中产生的文件
- step 2 :
cmake dir
, dir是CMakeLists.txt文件所在的路径,cmake是跨平台编译的工具; - step 3: 然后会在当前路径(build)下生成一个
Makefile
的文件,执行make
即生成对应的可执行文件 - step 4: 对于具体的可执行文件的使用方式
>>>./demo_vc
>>>输出 ./demo_vc vc.dat input.pcm lpcnet_feats.bin
1. 变量
1.1 变量的声明和定义
定义包含声明,声明不包含定义
1.变量的定义:变量的定义用于为变量分配存储空间,还可以为变量指定初始值。在一个程序中,变量有且仅有一个定义。
2.变量的声明:用于向程序表明变量的类型和名字。程序中变量可以声明多次,但只能定义一次。
3.两者联系与区别:
(1)定义也是声明,因为当定义变量时我们也向程序表明了它的类型和名字;
(2)但声明不是定义,可以通过使用extern关键字声明变量而不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern;extern位于函数外部的时候,才可以声明+定义,在函数内部不允许
4. 为什么要区分声明和定义
C++程序通常由许多文件组成。为了让多个文件访问相同的变量,C++区分了声明和定义
需要被别的文件用的变量,必须在本文件中定义好,比如在文件1中定义extern int i=0;然后才可以再别的文件中使用,使用方式是:在类体的外部使用extern int i;别的文件只能声明,而不能再定义
#include <iostream>
using namespace std;
// 变量声明 变量声明可以注释
extern int a, b;
extern int c;
extern float f;
int main ()
{
// 变量定义 变量定义不能注释
int a, b;
int c;
float f;
// 实际初始化
a = 10;
b = 20;
c = a + b;
cout << c << endl ;
f = 70.0/3.0;
cout << f << endl ;
return 0;
}
1.2 变量的作用域
全局变量: 在所有函数外部声明的变量
局部变量: 在函数或一个代码块内部声明的变量,局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值。
形式参数: 在函数参数的定义中声明的变量
变量的作用域有限,同时出现的时候,作用域小的屏蔽作用域大的,但是作用域小的结束作用区间后就失效了
#include <iostream>
using namespace std;
int b=5 ## 全局变量
int main()
{
int b = 2;
{
int b = 1;
cout << "b = " << b << endl; // 作用域结束即失效
}
cout << "b = " << b << endl;
cout << ::b << endl; // 5 ## 通过域名在函数中引用到全局变量,不加域名解析则引用局部变量
}
1.3 namespace命名空间
命名空间namespce定义:程序员各自在写代码时,可能会声明相同名字的函数或者变量,这样代码整体编译的时候就会报错—通过命名空间解决这一问题。
格式:
namespace name----命名空间的名字{
//variables, functions, classes---变量/函数/define等
}
使用方法:
第一种:直接调用
Li :: fp = fopen("one.txt", "r"); //使用小李定义的变量 fp
Han :: fp = fopen("two.txt", "rb+"); //使用小韩定义的变量 fp
第二种: using调用
using Li :: fp; --------“::”域操作解析符
fp = fopen("one.txt", "r"); //使用小李定义的变量 fp
Han :: fp = fopen("two.txt", "rb+"); //使用小韩定义的变量 fp
第三种:namespacea整体调用
using namespace Li;
fp = fopen("one.txt", "r"); //使用小李定义的变量 fp
Han :: fp = fopen("two.txt", "rb+"); //使用小韩定义的变量 fp
标准空间std
定义:将类、函数、宏等都统一纳入一个命名空间,这个命名空间的名字就是std。std 是 standard 的缩写,意思是“标准命名空间”。
使用:如果是在函数体中声明using namespace std;
,那么作用域只在这个函数内部有效;如果想要全局有效,需要开始声明(但是可能会有冲突,不建议)。
2. 关键字
2.1 extern
extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。
extern 用法说明
3. 常量
C++中设置一直不变的量叫常量,一般用大写字符表示常量
常量可以是任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值
1. define 定义
#define identifier value define 预处理器
#define WIDTH 5
#define NEWLINE '\n'
2. const 定义
const type variable = value; 声明指定类型的常量
const int WIDTH = 5;
const char NEWLINE = '\n';
3. 区别
void f1()
{
#define N 12
const int n = 12;
#undef N //取消宏定义后,即使在f1函数中,N也无效了
#define N 21//取消后可以重新定义
}
void f2 ()
{
cout<<N <<endl; //正确,N已经定义过,不受定义域限制
cout<<n <<endl; //错误,n定义域只在f1函数中
}
4. 函数
- 函数调用的顺序:C/C++ 编译 cpp 文件是从上往下编译,所以 main 函数里面调用其他函数时,如果其他函数在 main 函数的下面,则要在 main 函数上面先声明这个函数。
或者把 main 函数放在最下面,这个不仅限于 main 函数,其他函数的调用都是如此。被调用的函数要在调用的函数之前声明。
函数定义
return_type function_name( parameter list )
{
body of the function
}
- 返回类型: 一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。
- 函数名称: 这是函数的实际名称。函数名和参数列表一起构成了函数签名。
- 参数: 参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
- 函数主体: 函数主体包含一组定义函数执行任务的语句。
其中,parameter list中参数的名称可以省去不写,但是参数的类型必须声明
参数的传递方式
默认参数
定义:所谓默认参数,指的是当函数调用中省略了实参时自动使用的一个值,这个值就是给形参指定的默认值。
# 正确使用
void func(int a, int b, int c=20){ }
# 错误使用:默认参数只能放在形参列表的最后,实参和形参的传值是从左到右依次匹配的,而且一旦为某个形参指定了默认值,那么它后面的所有形参都必须有默认值。
void func(int a, int b=10, int c, int d=20){ }
函数重载
- 定义: 在实际开发中需要实现几个功能类似的函数,只是有些细节不同。比如交换两个数据(int/float/char等),不必要重新命名,可以让多个函数拥有相同的名字,只要它们的参数列表不同就可以。
- 函数的重载的规则:
(1)函数名称必须相同。
(2)参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
(3)函数的返回类型可以相同也可以不相同。 - 函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。
引用调用
向函数传递参数的引用调用方法,把引用的地址复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
#include <iostream>
using namespace std;
// 函数声明
void swap(int &x, int &y);
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
/* 调用函数来交换值 */
swap(a, b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
// 函数定义,实际上无返回值,但传参过来的a,b被修改了
void swap(int &x, int &y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
指针调用
#include <iostream>
using namespace std;
// 函数声明,传递的指针相当于一个参数
void swap(int *x, int *y);
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
/* 调用函数来交换值
* &a 表示指向 a 的指针,即变量 a 的地址
* &b 表示指向 b 的指针,即变量 b 的地址
*/
swap(&a, &b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
// 函数定义
void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 x 赋值给 y */
return;
}
setw() 函数
setw() 函数用于设置字段的宽度,仅对后方最近的起作用
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
// 开头设置宽度为 4,后面的 runoob 字符长度大于 4,所以不起作用
cout << setw(4) << "runoob" << endl;
// 中间位置设置宽度为 4,后面的 runoob 字符长度大于 4,所以不起作用
cout << "runoob" << setw(4) << "runoob" << endl;
// 开头设置间距为 14,后面 runoob 字符数为6,前面补充 8 个空格
cout << setw(14) << "runoob" << endl;
// 中间位置设置间距为 14 ,后面 runoob 字符数为6,前面补充 8 个空格
cout << "runoob" << setw(14) << "runoob" << endl;
return 0;
}
代码输出结果为
runoob
runoobrunoob
runoob
runoob runoob
5. 数组/字符串
5.1 数组
定义:
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
或者
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
容器vector
- 初始化
std::vector<double> values;
- 元素增减
#include <vector>
using std::vector
vector.push_back(),尾部添加
vector.pop_back() 尾部移除
- vector 输出不能一次性输出,循环打印
vector<int> path;
for (auto i : path)
std::cout << i << ' ';
- 容器的长度
int length = vector.size()-1
5.2 字符串
定义
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
或者
char greeting[] = "Hello";
char
- char是单个的字符
char a='a' ----注意使用的是单引号
string m="aaa" ----string使用的是双引号
5.3 string类
- string变量的定义
string s1; ******只是定义但没有初始化,默认是“”***********
string s2 = "c plus plus";
string s3 = s2;
string s4 (5, 's'); ********s4='sssss'********
- 字符串长度(string 的末尾没有’\0’字符)
string s2 = "sss";
int len = s.length(); *******len=3****
int len = s.size() 两种方法都可以
- 可以调用C语言的fopen,但是需要将字符串转成c类型
string.c_str()
string path = "D:\\demo.txt";
FILE *fp = fopen(path.c_str(), "rt");
- string的输入输出:输入运算符>>默认会忽略空格,遇到空格就认为输入结束
using std::string;
string s
while (getline(cin, s)) ----一直读取到输入结束
while (cin>>s) -----每个单词读取,空格断开(开头的空格不读取)
string类的访问和修改
- 可以用数组的形式访问和修改string
#include <iostream>
#include <string>
using namespace std;
int main(){
string s = "1234567890";
for(int i=0,len=s.length(); i<len; i++){
cout<<s[i]<<" ";
}
cout<<endl;
s[5] = '5';
cout<<s<<endl;
return 0;
}
输出
1 2 3 4 5 6 7 8 9 0
1234557890
- 拼接:用+来拼接字符串
string s1 = "first ";
string s2 = "second ";
string s5 = s1 + s2;
- 插入:格式
string1.insert(position, string2)
string s1='12345';
string s2='ss';
s1.insert(2,s2);
cout<< s1 <<endl;
输出
12ss345
- 删除字符串:string1.erase(position, delete_length)
position表示开始删除的字符位置;delete_length表示需要删除的字符长度,如果不写,会删除到该字符串结束
int main(){
string s1, s2, s3;
s1 = s2 = s3 = "1234567890";
s2.erase(5);
s3.erase(5, 3);
cout<< s1 <<endl;
cout<< s2 <<endl;
cout<< s3 <<endl;
return 0;
}
输出
1234567890
12345
1234590
- 提取字符串:string1.substr(position, length)
position:开始提取的字符串位置;length:提取的字符串长度
string s1 = "first second third";
string s2;
s2 = s1.substr(6, 6);
输出
second s1不发生改变
- 查找字符串:string1.find(string2, strart_position)
- find_first_of() 函数:查找字符串共同所有的字符首次出现的位置
6. 表达式
作用域符号::
A::member
就表示类A中的成员member
if
- 只有else if,没有elif;
- 注意分号的位置
if case1
do;
else if case2
do;
else
do;
7. 指针
指针是一个变量,其值为另一个变量的地址type *var_name;
#include <iostream>
using namespace std;
int main ()
{
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; // 在指针变量中存储 var 的地址
cout << "Value of var variable: ";
cout << var << endl;
// 输出在指针变量中存储的地址
cout << "Address stored in ip variable: ";
cout << ip << endl;
// 访问指针中地址的值
cout << "Value of *ip variable: ";
cout << *ip << endl;
return 0;
}
Value of var variable: 20
Address stored in ip variable: 0xbfc601ac
Value of *ip variable: 20
指针类型
- 空指针
*pr=NULL,pr=0
所有初始化未定义的指针都指向空 - 指针的算术运算:递增变量指针,以便顺序访问数组中的每一个元素
- 数组与指针
#include <iostream>
using namespace std;
const int MAX = 3;
例子1:
int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr;
// 指针中第一个元素的地址
ptr = var;
ptr++
}
#include <iostream>
using namespace std;
const int MAX = 3;
例子2:
int main ()
{
int var[MAX] = {10, 100, 200};
for (int i = 0; i < MAX; i++)
{
*var = i; // 这是正确的语法,数组名var本身就是地址
var++; // 修改 var 的值是非法的。这是因为 var 是一个指向数组开头的常量,不能作为左值。
}
return 0;
}
8、输入输出
fstream
-
std::ifstream ifs;
-
ifs.open(argv[2], std::ios_base::binary);
//以二进制形式打开文件argv[2]
//ifs.seekg()对输入文件定位,它有两个参数:第一个参数是偏移量,第二个参数是基地址。 -
ifs.seekg(0, ifs.end); /
/基地址为文件结束处,偏移地址为0,于是指针定位在文件结束处
//ifs.tellg() 不需要带参数,返回当前定位指针的位置,也代表着输入流的大小。 -
size_t fsize = ifs.tellg();
//sp为定位指针,因为它在文件结束处,所以也就是文件的大小 -
ifs.seekg(0, ifs.beg);
///基地址为文件头,偏移量为0,于是定位在文件头
//ios::beg:表示输入流的开始位置
//ios::cur:表示输入流的当前位置
//ios::end:表示输入流的结束位置
9. 类
本章节主要参考c语言中文网,好评!
9.1 类的声明
- 类的定义,相当于定义一种新的数据格式,等同于
int a;
float b
等声明; - 创建对象(等同于常说的变量名)
Student liLei;
- 类中有成员函数,成员变量;其中:成员函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前。
#include <iostream>
using namespace std;
class Box
{
public:
double length; //成员变量
void setWidth( double wid ); //成员函数
double getWidth( void );
private:
double width;
};
// 成员函数定义
double Box::getWidth(void)
{
return width ;
}
void Box::setWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
Box box;
// 不使用成员函数设置长度
box.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of box : " << box.length <<endl;
// 不使用成员函数设置宽度
// box.width = 10.0; // Error: 因为 width 是私有的
box.setWidth(10.0); // 使用成员函数设置宽度
cout << "Width of box : " << box.getWidth() <<endl;
return 0;
}
9.2 成员访问限定符 private/public/protect
- 如果既不写 private 也不写 public,就默认为 private。
- 类中向外暴露的接口(能通过对象访问的成员)都声明为 public
- private 关键字的作用在于更好地隐藏类的内部实现,只在类内调用,或者通过类的public 成员函数调用,而不能通过对象直接调用
- 成员变量大都以
m_
开头,这是约定成俗的写法,不是语法规定的内容。以m_
开头既可以一眼看出这是成员变量,又可以和成员函数中的形参名字区分开。 - 正确的示例
#include <iostream>
using namespace std;
//类的声明
class Student{
private: //私有的
char *m_name;
int m_age;
float m_score;
public: //共有的
void setname(char *name);
void setage(int age);
void setscore(float score);
void show();
};
//成员函数的定义
void Student::setname(char *name){
m_name = name;
}
void Student::setage(int age){
m_age = age;
}
void Student::setscore(float score){
m_score = score;
}
void Student::show(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
int main(){
//在栈上创建对象
Student stu;
stu.setname("小明"); //通过成员函数给私有变量m_name赋值
stu.setage(15);
stu.setscore(92.5f);
stu.show();
//在堆上创建对象
Student *pstu = new Student;
pstu -> setname("李华");
pstu -> setage(16);
pstu -> setscore(96);
pstu -> show();
return 0;
}
- 错误示例
Student stu;
//m_name、m_age、m_score 是私有成员变量,不能在类外部通过对象访问
stu.m_name = "小明";
stu.m_age = 15;
stu.m_score = 92.5f;
stu.show();
9.3 构造函数
- 定义:它的名字和类名相同,没有返回值,也不能被用户调用
- ==构造函数没有返回值!!!==因此(1)不管是声明还是定义,函数名前面都不能出现返回值类型,即使是 void 也不允许;(2)函数体中不能有 return 语句。
- 如果没有构造函数,需要多次调用getname,setname等才能为类内成员变量赋值;通过构造函数,可以在创建对象的同时为成员变量赋值;
- 示例(结合上一小节代码阅读)
//定义构造函数
Student::Student(char *name, int age, float score){
m_name = name;
m_age = age;
m_score = score;
}
int main()
{
//创建对象时向构造函数传参
Student stu("小明", 15, 92.5f);
}
9.3.1 构造函数的重载
- 一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。(一个对象只会调用一个构造函数,且创建对象时提供的实参必须和其中的一个构造函数匹配)
- 调用没有参数的构造函数也可以省略括号
- 默认构造函数:在调用时不需要传入实参的构造函数,包括(1)构造函数没有形参;(2)构造函数在定义行参的时候赋了初值;如果类内没有定义构造函数,编译器自动生成一个,称之为默认构造函数。
9.3.2 构造函数的参数初始化列表
- 定义:
- 格式:
class_name::Constructor_name(形参列表):初始化参数列表 {函数体}
//采用参数初始化表
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
//TODO:
}
- 注释:
m_name(name), m_age(age), m_score(score)
相当于函数体内部的m_name = name; m_age = age; m_score = score;
,也是赋值的意思。 - 注意:
(1)初始化参数列表要和类中成员变量的顺序保持一致,否则赋值会发生错误;
(2)初始化 const 成员变量的唯一方法就是使用参数初始化表。
9.4 析构函数
定义:一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行;名字与类名相同,格式~class_name()
9.5 对象指针
- 在栈上创建对象,形式和定义普通变量类似
Student stu; //定义一个Student类型的对象
Student *pStu = &stu; //pStu 是一个指针,它指向 Student 类型的数据
- 在堆上创建对象,必须要用一个指针指向它,记得 delete 掉不再使用的对象。
Student *pStu = new Student;
//使用 new 在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数。
- 通过对象名字访问成员使用点号
.
,通过对象指针访问成员使用箭头->
9.6 this指针
- 定义:this是一个关键字,它指向当前对象,通过它可以访问当前对象的所有成员,包括 private、protected、public 属性的。
- 也是一个 const 指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是不允许的。
- this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的。是成员函数和成员变量关联的桥梁。
- 只有当对象被创建后 this 才有意义,因此不能在 static 成员函数中使用(后续会讲到 static 成员)。
#include <iostream>
using namespace std;
class Student{
public:
void setname(char *name);
void setage(int age);
void setscore(float score);
void show();
private:
char *name;
int age;
float score;
};
void Student::setname(char *name){
this->name = name;
}
void Student::setage(int age){
this->age = age;
}
void Student::setscore(float score){
this->score = score;
}
void Student::show(){
cout<<this->name<<"的年龄是"<<this->age<<",成绩是"<<this->score<<endl;
}
int main(){
Student *pstu = new Student;
pstu -> setname("李华");
pstu -> setage(16);
pstu -> setscore(96.5);
pstu -> show();
return 0;
}
运行结果李华的年龄是16,成绩是96.5
9.7 inline函数
- 作用:函数之间的跳转是有时间和空间的开销,将那些短小的、频繁调用的函数声明为内联函数,可以消除函数调用的时空开销。
- 在类体中和类体外定义成员函数是有区别的:在类体中定义的成员函数会自动成为内联函数(不需要加inline),在类体外定义的不会;
- 如果既希望将函数定义在类体外部,又希望它是内联函数:在类体外定义函数时加 inline 关键字,声明时添加关键字无效
举个例子
//声明内联函数
void swap1(int *a, int *b); //也可以添加inline,但编译器会忽略
//定义内联函数
inline void swap1(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
- Tips: 只有当函数只有 10 行甚至更少时才将其定义为内联函数(不允许有循环和开关语句),主要是为了程序运行的效率问题
//函数定义,这样say就变成一个内联函数
inline void Student::say(){
cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
}
9.8 static静态成员变量
- 定义:静态成员变量是一种特殊的成员变量,它被关键字static修饰,实现多个对象共享数据的目标
- static 成员变量不占用对象的内存,而是在所有对象之外(全局数据区)开辟内存,即使不创建对象也可以访问。如果没有初始化赋值,默认是零;而对于堆区/栈区的数据,如果没有初始赋值,会默认是垃圾数据;
- 注意: static 成员变量必须在类声明的外部初始化,格式
type class::name = value;
,比如int Student::m_total = 0;
,静态成员变量在初始化时不能再加 static,但必须要有数据类型。被 private、protected、public 修饰的静态成员变量都可以用这种方式初始化。 - static变量可以通过对象名访问,也可以通过类名访问,但要遵循private/public的访问原则
//通过类类访问 static 成员变量
Student::m_total = 10;
//通过对象来访问 static 成员变量
Student stu("小明", 15, 92.5f);
stu.m_total = 20;
//通过对象指针来访问 static 成员变量
Student *pstu = new Student("李华", 16, 96);
pstu -> m_total = 20;
9.9 静态成员函数
- 静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
9.10 const关键字
const常量/常函数/const对象
- 定义:const修饰的成员函数or成员变量不能被修改,
- const 成员变量:只能通过参数初始化列表去初始化
- const 成员函数:可以使用类中的所有成员变量,但是不能修改它们的值,需要在声明和定义的时候在函数头部的结尾加上 const 关键字
//类内声明常成员函数
char *getname() const;
//定义常成员函数
char * Student::getname() const{
return m_name;
}
- const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的 const 成员了。
const对象
const class object(params);
const指针
const class *p = new class(params);
9.11 拷贝函数
#include <iostream>
using namespace std;
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数(默认构造函数和析构函数即使不写,在编译的时候也会自动生成)
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "调用构造函数" << endl;
// 为指针分配内存,为类中私有变量赋值,根据函数自行定义,不是必须的
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
Line::~Line(void)
{
cout << "释放内存" << endl;
delete ptr;
}
int Line::getLength( void )
{
return *ptr;
}
void display(Line obj)
{
cout << "line 大小 : " << obj.getLength() <<endl;
// 在跳出函数之前,执行class Line的析构函数
}
// 程序的主函数
int main( )
{
Line line(10);
// 调用构造函数 Line( int len ); 执行 cout << "调用构造函数" << endl;
display(line); // Line obj=line, obj的对象是类 Line, 复制给line,因此是复制构造函数
return 0;
}
9.12 friend友元函数和友元类
- 定义:可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。
- 注意:友元函数不同于类的成员函数,在友元函数中不能直接访问类的成员,必须要借助对象
- (1)当作全局函数用法示例,注意传的分别是
&stu和pstu
#include <iostream>
using namespace std;
class Student{
public:
Student(char *name, int age, float score);
public:
friend void show(Student *pstu); //将show()声明为友元函数
private:
char *m_name;
int m_age;
float m_score;
};
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
//非成员函数,不能直接访问类的成员,而是通过对象
void show(Student *pstu){
cout<<pstu->m_name<<"的年龄是 "<<pstu->m_age<<",成绩是 "<<pstu->m_score<<endl;
}
int main(){
Student stu("小明", 15, 90.6);
show(&stu); //调用友元函数,通过对象,本身是传地址
Student *pstu = new Student("李磊", 16, 80.5);
show(pstu); //调用友元函数
return 0;
}
(2)将其他类的成员函数声明为友元函数
- 定义:一个函数可以被多个类声明为友元函数,这样就可以访问多个类中的 private 成员
#include <iostream>
using namespace std;
****要在Student中使用Address类,所以提前声明*****
class Address; //提前声明Address类
//声明Student类
class Student{
public:
Student(char *name, int age, float score);
public:
****Student 类的成员函数 show() 声明为 Address 类的友元函数*******
*****show() 就可以访问 Address 类的 private 成员变量了****
void show(Address *addr);
private:
char *m_name;
int m_age;
float m_score;
};
//声明Address类
class Address{
private:
char *m_province; //省份
char *m_city; //城市
char *m_district; //区(市区)
public:
Address(char *province, char *city, char *district);
//将Student类中的成员函数show()声明为友元函数
friend void Student::show(Address *addr);
};
//实现Student类
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(Address *addr){
cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl;
cout<<"家庭住址:"<<addr->m_province<<"省"<<addr->m_city<<"市"<<addr->m_district<<"区"<<endl;
}
//实现Address类
Address::Address(char *province, char *city, char *district){
m_province = province;
m_city = city;
m_district = district;
}
int main(){
Student stu("小明", 16, 95.5f);
Address addr("陕西", "西安", "雁塔");
stu.show(&addr);
Student *pstu = new Student("李磊", 16, 80.5);
Address *paddr = new Address("河北", "衡水", "桃城");
pstu -> show(paddr);
return 0;
}
注意:关于友元,有两点需要说明
- 友元的关系是单向的而不是双向的。如果声明了类 B 是类 A 的友元类,不等于类 A 是类 B 的友元类,类 A 中的成员函数不能访问类 B 中的 private 成员。
- 友元的关系不能传递。如果类 B 是类 A 的友元类,类 C 是类 B 的友元类,不等于类 C 是类 A 的友元类。
模板
函数模板
- 定义:函数模板实际上是一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型
using namespace std;
********template<typename T>被称为模板头*************
*********语法规定:其中typename 也可以替换成class,是一样的(是早期不严谨的用法,现基本用typename)***********
template <typename 类型参数1 , typename 类型参数2 , ...> 返回值类型 函数名(形参列表){
//在函数体中可以使用类型参数
}
***********函数模板的定义**************
template<typename T> void Swap(T *a, T *b){
T temp = *a;
*a = *b;
*b = temp;
}
************也有先声明,再定义的**************
//声明函数模板
template<typename T> T max(T a, T b, T c);