C++学习笔记

本文为本人本科时学习C++的笔记。

1.CPP入门

面向对象程序设计(Object Oriented Programming)

比特C++课程结构:
C++的基本语法 1.入门 2.类和对象 3.动态内存管理 4.模板
STL库------STL是Standard Template Library的简称,中文名标准模板库
高阶数据结构

C++的三个重要版本:
C with classes 1979年 加了类的C
C++ 98标准版本,有公司还在用这个版本
C++ 11增加了许多特性,使得C++更像一种新语言

缺省参数:

全缺省参数:每个参数都有初始值,主函数里比如3个参数只给了2个,按从左往右赋值,没轮到的用默认值
半缺省参数:部分参数有初始值.只能从右往左依次给初始值.不能int a = 1,int b,int c = 3.

缺省值必须是常量或者全局变量

缺省参数在声明与定义不能同时赋缺省值.在声明里给最好.两个备胎见面会打架,值不同.
声明和定义区别----;和{}

问:为什么C语言不支持重载?编译器对于函数名字的处理方式过于简单,同名函数编译中名字都是_Add,区分不开,C++更复杂,解决了这个问题.

重载:
extern “C”
加extern "C"的话就是将一个函数C风格编译,函数名编译时加的是头_

函数重载,必须在相同作用域下(大前提),并且函数名相同,参数列表(参数个数 或 类型 或 顺序)不同.
有重名函数的话,编译时编译器会根据你数据相应类型推演后自己选择用哪个:
Add(1,2);
Add(1.0,2.0);
Add(‘1’,‘2’);//没声明相应的重载的话会转换类型,char–>int
Add(1,3.14);//报错,既可以int转double,也可以double转int,多种选择的话编译器就报错让人工进行修改
Add(1,(int)3.14);通过,你强转了.
总结:能转就转,单个转换方式没事,有多个转换方式会报错

void Test()
{}
void Test(int a = 10)
{}
int main()
{
    Test(100);//没问题,使用第二个
    Test();//报错,两个都行
}
//这样也是重载.
//总结:无参函数与同名的全缺省函数不能同时存在.

内联函数(宏):
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销, 内联函数提升程序运行的效率。

//用const定义常量,能检测类型.
int main() {
       const int a = 10;
       int* pa = (int*)& a;//这个(int*)
       *pa = 100;
       cout << a << endl;//编译阶段用10替换了a
       cout << *pa << endl;//a空间确实已经变成了100
       return 0;
}//结果确实很诡异,但是不知道它想说什么。const宏替换在预处理阶段?

宏的替换在预处理阶段,内联函数在编译时.前者无类型检测,后者有.
Debug版本就不展开,还是开辟空间,要让调试.Release版本就是优化过的.
inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

宏的优缺点?
优点: 1.增强代码的复用性。 2.提高性能。
缺点: 1.不方便调试。(因为预编译阶段进行了替换) 2.导致代码可读性差,可维护性差,容易误用。 3.没有类型安全的检查 。
C++有哪些技术替代宏? enum/const替换宏常量 inline替换宏函数.

C++11新特性:

auto 是语法糖–>简化代码
0.auto必须初始化.
1.用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
2.当在同一行内声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
3.auto不能作为函数的参数
void Test(auto a);//不行,参数类型必须明确.因为缺省参数,编译阶段要编函数名字。
4.auto不能直接用来声明数组
auto arr[] = {1,2,3};//不行.既不知道类型,又不知道元素个数.
5.auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

for循环
注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。

  1. for循环迭代的范围必须是确定的.对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
  2. 迭代的对象要实现++和==的操作。//以后讲

空值指针nullptr:
指针必须初始化,或者设为NULL,否则就是没有对象的野指针
但是NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量
nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转化为指针类型
在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

2.类与对象,this指针

第一关注的是类,对于不同的客户选择性的将接口提供给用户使用.
例如:美团–用户,骑手,商家需要的功能不一样.

封装的本质是管理
1,数据和方法都封装到类里,方便管理
2,访问限定符(public…)

低耦合,高内聚.—好修改代码—

声明放在.h,定义放在.cpp中.
问:什么是声明和定义?
再问:为什么要分离?–保护产权,只给客户.h和动/静态库,用户可以只看.h就知道功能列表.

类:
类和结构体是图纸,构造就是实例化出一栋建筑.
类中的元素称为类的成员
类中的数据称为类的属性或者成员变量
类中的函数称为类的方法或者成员函数
class DATE{…};
DATE A;//DATE是类,A是对象,A的类型是DATE,整句话叫做声明.
类形成了域

在类体外定义成员,需要使用 :: 作用域解析符 指明成员属于哪个类域。
想使用类里private里的东西,可以void Person::Test,只要在类里面声明Test,就是自己人.

问:C++中struct和class的区别是什么?
答:C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。 和class是定义类是一样的,区别是struct的成员默认访问方式(没加访问限定符的情况下所有成员都)是public,class的成员默认访问方式是private。
C语言里结构体只能定义变量,在C++结构体中可以定义函数
C中要写"struct A",.cpp中可以去掉struct只写A了.

类:成员函数不算空间.类的大小实际是成员变量之和,但是要内存对齐
只有成员函数的类空间和空类的大小都是1;//占位,标注自己的存在

this指针:
隐含的this指针.隐含意味着就不能写出来,声明的时候()里不允许自己指.但可以打印这个this指针.鼠标放声明上能看到参数列表.
this指针的类型:类类型* const,表明this的指向不能变
this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

A* p=NULL;
p->Show();
此时类里即为
Void Show(A* this);
这this确实是空指针,但是只要没有访问,就不会有问题

3.类的默认成员函数

两个栈,一个是数据结构,一个是虚拟进程地址空间分段.但是性质都是后进先出.

类名 参数列表:初始化列表 {
}

构造函数是特殊的成员函数
构造函数的主要任务并不是开空间创建对象,而是给各对象赋初始值.
一旦进入到构造函数体,对象已经创建成功.初始化由初始化列表完成.
其特征如下: 1. 函数名与类名相同。 2. 无返回值。 3. 对象实例化时编译器自动调用对应的构造函数。 4. 构造函数可以重载。
如果类中没有显式定义构造函数,编译器会自动生成无参的默认构造函数,一旦用户显式定义编译器将不再生成.但是使用会随机值,

C++把类型分成内置类型/基本类型 和 自定义类型.
基本类型不做处理,自定义类型会初始化—调用A的构造函数.

无参构造函数、全缺省构造函数、我们没有写而编译器默认生成的构造函数,都可以认为是默认构造函数。并且默认构造函数只能有一个。
–>>不用传参的就是默认构造函数.全缺省的最好用

析构函数

析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。
而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作.

  1. 析构函数名是在类名前加上字符~。 2. 无参数无返回值。 3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。 4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

构造的时候先构造A,再构造B,析构的时候先析构B,再析构A.

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

1.拷贝构造函数是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
3.若未显示定义,系统生成默认的拷贝构造函数.默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝(对应的也有深拷贝)
会原封不动拷贝,包括指针,其他东西已经拷贝,然而指针还是指向原地址,如果析构,原空间就会重复释放两次,就崩溃了.s2已经把空间释放了,s1还不知道他已经是个野指针了.
解决办法以后讲.

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
*
不能通过连接其他符号来创建新的操作符:比如operator@
*
重载操作符必须有一个类类型或者枚举类型的操作数
*
用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
*
作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参
*
.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

赋值运算符主要有四点: 1. 参数类型 2. 返回值 3. 检测是否自己给自己赋值 4. 返回*this 5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。

const成员函数中不能修改成员变量

void Test()const里this指针类型实际上是const Date* const,不能修改类里的任何成员
void Test()的则是 Date* const, 这两个this指针的指针类型是不同的
所以可以形成重载.因为参数列表不同.

请思考下面的几个问题:
(加了const的函数可认为是只读函数,不可修改里面内容,不加const的是可读可写函数.)

  1. const对象可以调用非const成员函数吗?–no,加const只读,普通类型可读可写.

const Date cd(2019,8,17);
cout<<&cd<<endl;
cd就是const对象,调用的取地址函数是可读可写的,如果可写,就和const函数不可写的初衷相违背,所以不可以调用.
2. 非const对象可以调用const成员函数吗?–y
3. const成员函数内可以调用其它的非const成员函数吗?–n 加了const就不能修改,调用的非const成员函数却可以修改,还是有违初衷.

class Date{
public:
    void Print()const{
    }
};

这个print就是const成员函数,这样的函数不可以修改成员变量.只读.
4. 非const成员函数内可以调用其它的const成员函数吗?–y
总结:const不能调用普通.

调用重载的函数,看哪个更匹配,可读可写调用可读可写,只读的调用只读.

4.new/模板/初始化列表

自定义类型对运算符是不支持的,都需要运算符重载

动态内存管理:
在C++中使用malloc申请内置类型空间没问题
Date* pd1=(Date*)malloc(sizeof(Date));
free(pd1);
但是只是创建了一块和Date一样大小的空间,之后释放掉了,没有调用构造函数,也没有调用析构函数.所以malloc开辟的空间并不是我们所需要的类类型对象空间,只是大小一样而无任何功能.

new/delete会调用构造函数和析构函数.
new不需要判空.
new/delete: //申请单个对象空间

Date* pd2=new Date;
delete pd2;

Date* pd3=new Date[10];//开辟10个连续的Date类型空间
delete[] pd3;//释放一段连续空间.

构造函数和析构函数都被调用了10次.

int* p1 = new int;//开辟一个整形空间,不初始化就是随机值
int* p2 = new int(10);//并且对此整形初始化.
int* p3 = new int[10];//开辟连续的10个整形空间,不初始化就是随机值
int* p4 = new int[10]{0,1,2,3,4,5,6,7,8,9};//并且对这些空间进行初始化.
delete p1;
delete p2;
delete[] p3;
delete[] p4;

模板:
问:实现一个通用的加法函数(任何类型的数据都可以)
函数重载int double char. 缺陷:复用率低,不好维护
那么可以用函数模板.(还有类模板)

template<typename T>   //模板参数列表
T Add(const T& left,const T& right){
    return left + right;
}

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用,不同类型生成位置不同,同类型代码只生成一份.
隐式实例化,自己推演.
Add(1,(int)2.0);//√
Add(1,2.0);//√显式实例化,直接指定用哪个类型,不推演.1被隐式转换成double.

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数.使用时优先调用用户写的那个.
    Add(1,2);//用用户新写的
    Add(1,2);//用模板生成的.显式实例化
    Add<>(1,2);//用模板生成的.隐式实例化
  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板
    Add(1,2.0);//模板里是T1,T2.而新写的是int,那么肯定使用模板给的.
  3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
    Add(c1,c2);c1 c2是类创建的对象,是自定义类型,不行.

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

模板的编译分为两个阶段:
1.实例化前—先对模板进行简单的语法检测,不做深入的编译
2.实例化后—编译器根据T的实际类型生成代码,编译.

只有构造函数有初始化列表,拷贝构造函数也是构造函数,所以也有.
初始化不写的话也存在,初始化的是随机值.
初始化列表:

Date(int year = 2019, int month = 8, int day = 1)
_year(year)
, _month(month)
, _day(day) //每个变量只能初始化一次.
{
//这里就不用再赋初值了,可以用来做别的事.
}

每个"成员变量"后面跟一个放在括号中的初始值或表达式。

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

类中包含以下成员,必须放在初始化列表位置进行初始化:
*
引用成员变量 —引用必须初始化
*
const成员变量 —const类型常量必须初始化.也只有初始化列表可以完成此过程.
*
类类型成员(该类没有默认构造函数)—此类已经定义了带有参数的构造函数,编译器不会生成默认的无参构造函数.如果不初始化,编译器会调用此类的无参构造函数,而此无参构造根本未生成,不存在,自然会报错.

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关.

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用:
编译器使用Date类的构造函数,将2019转换为无名日期类型对象,然后用这个无名对象给d赋值.赋值结束后就释放了.

编译器什么情况下才会给一个类生成默认的构造函数?
编译器觉得需要的时候.如果生成出来有作用就生成,没作用就不生成.
四个场景…

5.STL简介

STL(standard template libaray-标准模板库), 不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

STL六大组件:
1.
容器(本质:常见数据结构的封装):存放数据
2.
算法:

	* 

通用算法(迭代器的功劳):与操作的数据类型无关(函数模板) + 与数据结构无关
*
与数据结构相关算法
3.
迭代器
4.
配接器(适配器):

	* 

容器适配器 stack栈 queue队列
5.
仿函数(函数对象):可以像函数调用一样使用的对象.作用:让一个算法更加灵活
6.
空间配置器:高效管理空间,申请/释放.

容器:
*
序列式容器(底层为线性结构,还有底层为红黑树和哈希表结构的在STL进阶部分讲):对线性数据结构的封装.

	* 

string:字符串,动态顺序表(笔试最多)
*
array:静态顺序表,C++11
*
vector:动态顺序表(笔试最多)
*
list:带头节点的双向循环链表
*
forward_list:带头节点的循环单链表.C++11
*
deque:动态二维数组
*
(容器)适配器:

	* 

stack 栈
*
queue 队列
*
priority_queue

6.string

string:管理字符串string如何解决浅拷贝—string的模拟实现时讲解C++封装string类的理由:
1. C字符串和操作字符串的方法分开,不符合面向对象思想
2. 使用麻烦,要记忆/查阅
3. 部分C字符串操作不安全,拷贝需要空间足够大,一旦越界就崩溃.

string类结构:动态顺序表(空间 size capacity),\0结尾,动态增长.string类重点:
1. 构造
2. 容量
3. 元素访问
4. 修改
5. 特殊操作
6. 迭代器

string s;//构造一个空字符串.string以空格和回车作为字符串之间的分割getline(cin,s1);//获取一行,控制台输入,s1接收.size和length都是返回有效字符个数.//本来string类只有length,但是其他容器都有size,所以加了进来,统一标准resize,缩小size时不会缩小capacity,麻烦,后面再扩容又要开辟拷贝销毁.resize:改变有效元素个数,可能需要扩容// 将s中有效字符个数增加到15个,多出位置用缺省值’\0’进行填充// “aaaaaaaaaa\0\0\0\0\0”// 注意此时s中有效字符个数已经增加到15个s.resize(15);reserve:改变空间大小初始化后立即s.reserve(20);里面并没有有效元素,访问就会崩溃.如果知道需要大概多少容量,一开始就reserve好,避免后面多次扩容造成资源浪费.clear,只清空有效字符,不改变capacity.

迭代器iterator–类似于指针

string::iterator it = s.begin();
aoto it = s.begin();//auto简单.

begin,end正向迭代器,++操作,迭代器从begin向end方向移动
rbegin在end的位置,rend在begin的位置.++操作就是从end往begin移动.
[begin,end)左闭右开
reverse(s.begin(),s.end());
正向遍历string类
反向遍历用rbegin和rend.

operator+= (重点,用的最多) 在字符串后追加字符串
String s;
String str("YOU ");
s += ‘I’;//追加字符
s += " LOVE ";//追加C风格字符串
s += str; //追加string类对象 //最终s里内容就是I LOVE YOU

尾插
s.push_back(‘!’);

find和insert
size_t pos = s.find(‘!’);
if(pos != string::npos){
s.insert(pos,“_”);
}//在某个字符前插入字符串
find返回值size_t,如果没有找到,返回npos,npos恒为-1.
// npos是string里面的一个静态成员变量 // static const size_t npos = -1;
rfind反方向查找.

删除
s.erase();
s.erase(s.begin(),s.end());
s.clear();//功能全部一样,删除全部有效字符.

交换
swap(s,str);
s.swap(str);//两个是一样的.用第二个,只需要改变两个指针,第一种是用第三方交换.
交换两个指针比拷贝三次内容效率高太多.

substr截取.

对于string类使用最多的遍历方式:

for(size_t i = 0; i < s.size(); ++i) 
cout<<s[i]<<endl;

7.vector

#include<vector>

//初始化方法:
vector<int> v1();
vector<int> v2(10,5);//十个元素,都是5
vector<int> v3(v2.begin(),v2.end());//用v2构造v3
//区间构造↓
int array[] = {0,1,2,3,4,5,6,7,8,9};
vector<int> v4(array,array+sizeof(array)/sizeof(array[0]));
//用一个数组构造v4
vector<int> v5(v4);

vector<int> v6{0,1,2,3,4,5,6,7,8,9};

1.for循环遍历
2.采用迭代器遍历
3.for(auto e : v4)
cout << e << " ";
cout << endl;

扩容方式,g++下是标准的翻倍

迭代器失效:
扩容前定义的的begin/end在扩容后还指向的原来空间,使用就是访问越界
迭代器本质就是指针,指针指向的空间非法.
只要容量发生了变化,就有可能导致迭代器失效,可以在扩容后再写一句
it = v.begin();//给迭代器重新赋值.

swap也会导致地址改变.失效.
assign赋值也会.
insert.
earse删除元素后迭代器失效.earse删一个元素,返回下一个元素位置
it = v.erase(it);

resize对内置类型未初始化的默认值是0.

8.list

list底层结构:带头节点的双向循环链表
链表/list不支持随机访问.
string/vector支持随机访问
list常用接口:
*
构造 拷贝 析构
*
迭代器操作
*
容量操作
*
元素访问
*
元素修改
*
特殊操作

#include

list L1(10,5);//10个元素,都是5.
list L2(10);//10个元素,都是0.
//如果list里放的是内置类型元素,第二个参数未提供的话编译器默认赋0.
//如果是自定义类型,第二个参数未提供,编译器需要调用该类的无参构造函数创建无名对象,可是我们已经写了一个有参构造函数,编译器不会再自己生成任何构造函数,就报错"没有合适的构造函数可用".那么可以将构造函数改为全缺省,此时就可以编译通过,每个元素默认赋缺省值.

//迭代器类型:int*
int array[]={0,1,2,3,4,5,6,7,8,9};
list<int>L3(array,array+sizeof(array)/sizeof[0]);

//迭代器类型:vector<int>::iterator(可以用auto代替)
vector<int> v{0,1,2,3,4,5,6,7,8,9};
list<int> L4(v.begin(),v.end());

list<int> L5(L3);
//通过正向迭代器遍历打印链表所有节点
list<int>::iterator it = L2.begin();
while(it != L2.end()){
cout<<*it<<" ";
++it;
}
//反向迭代器反向遍历打印
list<int>::reverse_iterator rit = L3.rbegin();
while(rit != L3.end()){
cout<<*rit<<" ";
++rit;
}
//使用范围for:[begin,end)正向打印.范围for无法反向.
for(auto e : L4)
    cout<<e<<" ";
cout<<endl;

front/back 首元素/尾元素

9.继承

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显式调用。
    (如果基类具有带有参数的构造函数时,派生类必须显式定义自己的构造函数,并且在其初始化列表的位置显式调用基类的构造函数)
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类 对象先清理派生类成员再清理基类成员的顺序。
  5. 派生类对象初始化先调用基类构造再调派生类构造。
  6. 派生类对象析构清理先调用派生类析构再调基类的析构

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

多继承,public B1,public B2
这样写才都是public,pbulic B1,B2 意味着B2未指定是哪种,class默认继承下来是private
,
多继承会先继承B1的成员变量,再继承B2,最后是自己.内存空间上是累加的,各定义了一个int那么自己大小就是12.

菱形继承可理解为亲兄妹生的孩子…

缺点:二义性,person的成员被继承了两次
解决方法一:
a.Student::_age = 1;
a.Teacher::_age = 2;
解决方法二:虚拟继承
class D : virtual public B

虚拟继承:
1.比普通继承多4个字节大小
2.与普通继承模型相倒,基类在下,派生类在上
3.编译器为派生类生成了默认构造函数,作用:在构造函数中必须将对象的前4字节初始化好.

10.static友元内部类

静态成员变量:
在类中声明时必须添加static,必须在类外来定义()
静态成员变量不包含在对象中,不会影响对象的大小.不能在初始化列表初始化.

  1. 静态成员为所有类对象所共享,不属于某个具体的实例
  2. 静态成员变量必须在类外定义,定义时不添加static关键字
  3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值
    静态成员函数不能使用const修饰.const修饰的是this指针,他没有.
    静态成员函数不能调用非静态成员函数,没有this,传不过去?
    非静态成员函数可以调用静态成员函数,也可以调用静态成员变量.

输出运算符重载:
<<不能重载成类的成员函数
因为<<运算符重载的第一个参数必须是ostream的对象,如果重载成类成员函数则第一个参数就是隐藏的this指针,
ostream& operator<<(ostream& _cout){
_cout<<_year<<“-”<<_month<<“-”<<_day;
return _cout;
}//这样不对,只能按d<<cout;使用,打印方式和常规打印相反
可以在类外给成全局函数:
ostream& operator<<(ostream& _cout,const Date& d){
_cout<<d._year<<“-”<<d._month<<“-”<<d._day;
return _cout;
}//

友元
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
*
友元函数可访问类的私有成员,但不是类的成员函数
*
友元函数不能用const修饰(不是成员函数就没有this)
*
友元函数可以在类定义的任何地方声明,不受类访问限定符限制(访问限定符限定的是类里面的成员,友元函数不是类的成员.不能放在函数体里面)
*
一个函数可以是多个类的友元函数
*
友元函数的调用与普通函数的调用和原理相同

友元类
friend class Date;//写在谁里面就是可以访问谁的私有成员. 写在Time类里,说明Date是Time的好朋友,知道它的private.
友元类的所有成员函数都可以是 另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性.Time却不知道Date的private,别人不把你当朋友.
友元关系不能传递.朋友的朋友不是朋友.
友元关系也不能继承!

友元的优点:提高程序的效率(没友元的话<<重载就要函数调用来实现,没有直接访问成员快)
友元的缺点:破坏了类的封装性,增加了耦合度

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值