链接说明:语言学习初学者的困惑与其对应的解决方案
一般开发游戏使用的语言:
2D页游:AS3 JS
3D页游:AS3 C#(Unity)
IOS游戏:Obj-C js/lua(Cocos-2d-x) js/C#(Unity) AS3
安卓游戏:java js/lua(Cocos-2d-x) js/C#(Unity) AS3
在线小游戏:AS3 JS
大型单机游戏/客户端MMORPG::C++ C#
为什么育碧之类的大公司依然坚定的用C++?
1 工业化好:上中下游都直接首先支持C++,民间普及度也高
2 可以手动控制各种细节,优化的空间非常大。同样的游戏,肯定优先选更流畅逼真的。
3 可以更好的团队开发。
如上是笔者个人提出的问题,但相比而言有自己的作品和扎实的基础才是面试游戏公司的底气。
这个问题是错误的,只有机器语言才能对硬件直接进行操作。
C语言接近硬件是指它可以直接用指针访问硬件地址。
所以,相对来说,C语言在高级语言当中是最贴近物理层面的语言。
C语言实现面向对象学习链接 1
C语言实现面向对象学习链接 2
解释命名空间的链接
使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突。
在C++中,变量、函数和类都是大量存在的。
如果没有命名空间,这些变量、函数、类的名称将都存在于全局命名空间中,会导致很多冲突。
目前初学,记住这句话
允许命名空间名字相同,但在此基础上不能允许出现同名变量,否则会报错重复定义。
举例说明:在main.cpp使用其他文件的命名空间
//func1.cpp
#include
namespace A
{
int a = 5;
}
void func1()
{
std::cout << A::a << std::endl;
}
//func2.cpp
#include
namespace B
{
int a = 5;
}
using namespace std;
void func2()
{
cout << B::a << endl;
}
//main.cpp
#include
//有了如下两行声明才可使用
extern void func1();
extern void func2();
using namespace std;
int main()
{
func1();
func2();
}
源文件不能访问其他源文件的命名空间
#pragma once
//等同于
#ifndef __FUNC1_H
#define __FUNC1_H
#endif
#include
using namespace std;
int main(void)
{
int a,b;
cin >> a >> b;
cout << "a=" << a << ",b=" << b << endl;
char str[100];
char ch = cin.get();
printf("ch=%d",ch);
cout << "ch= " << ch << endl;
cin.getline(str,100);
return 0;
}
for(int i=0;i<2;i++)
{
.....
}
①
register:尽可能将变量保存到cpu内部寄存器中,从而省去了cpu从内存获取的时间,提高了运行效率
注意事项:
1:只能修饰局部变量,不能修饰全局变量和函数;
2:register 修饰的变量不能通过&获取变量的地址:(C++升级:当用&获取变量地址时,该变量不会保存到寄存器中)
3:register 修饰的变量类型一定是cpu所能接受的数据类型!
②
const:C语言中,将const修饰的变量变为只读变量。常应用在修饰函数形参里,保护实参不被修改。
C++升级:const修饰的变量就是常量。const地址必须由const指针保存(const对const )
记住两点 : 1 常量 2 .上面升级的整句话
③
typedef:提高代码移植性 提高编码效率 给数据类型重命名;
思考 C语言:使用typedef如何重命名函数指针;
C语言 typedefn int (* P_FuNC)(int,int); C++升级:using P_FUNC2 = int(*)(int ,int );
④
auto:自动变量:强调变量的生命周期
C++升级:auto:类型推导符,目标:现代化标称(提高开发效率) 很强!!!!!!
⑤
malloc 实现原理?free 如何知道要释放多少内存空间? C++ new delete
malloc是按照字节为单位分配,new按照数据类型为单位分配空间;
malloc是函数,new是运算符;
malloc只分配空间,不初始化,new既可以分配空间又可以初始化。
⑥
一维数组名的作用:保存数据首个元素的地址
二维数组名的作用:保存数据首个一维数组的地址
三维数组名的作用:保存数据首个二维数组的地址
什么时候传值?什么时候传地址?
参考链接:https://www.jianshu.com/p/d19fc8447eaa
C98标准都是左值引用;
简单判定左值与右值方法: 能取地址便是左值,不能则是右值;
右值引用的符号是 && 左值引用的符号是 &;
1.引用的注意事项:
2.引用可以作为形参,解决了传参和传地址;
3.作返回值时函数调用可作为左值;
4.引用是否占用内存空间?
5.面试问题:指针和引用的区别;
也可以查阅链接:https://blog.csdn.net/boy_of_god/article/details/81022316
6.面试问题:为什么会出现引用?
结论:直接的原因是为了支持运算符重载
也可以查阅链接:https://www.cnblogs.com/lfri/p/12695938.html
7.左值引用,右值引用;左值右值如何判断?
8:引用:什么时候加const? 什么时候不加const?
也可以查阅链接:https://blog.csdn.net/sammie123321/article/details/96426851
#include
using namespace std;
//引用可以作形参
void func1(int &a,int &b)
{
int temp = 0;
temp = a;
a = b;
b = temp;
}
//引用作为返回值 函数可以作左值
int & func2(const int &a)
{
int num = 5; //此时会警告,返回一个局部变量,改变如下注释:
//static int num = 5;
return num;
)
int main(void)
{
int a = 5,b = 6;
func1(a,b)
cout << a << "," << b << endl;
func2(a) =10;
cout << a << endl;
int num = 5;
int &l_num = num;
//int &l_num = 5;//测试左值引用绑定左值,报错
//static int &l_num = 5;解决左值错误问题;
int &&r_rnum = 5;//右值引用绑定右值
//int &&r_rnum = num;//测试右值引用绑定左值,失败,报错;
//int &&r_rnum = std::move(num);//右值改成左值 成功
//但这里发生了 对象移动
// 对象移动解决的是对象拷贝的开销问题
return 0;
}
1.时间换空间,空间换时间
也可以查阅链接:https://blog.csdn.net/cui929434/article/details/98033039
2.用编译时间换内存空间?(宏函数)
#define MAX(a,b) ((a) > (b)) ? (a) : (b)//傻瓜式替换,预处理阶段
//如上增加了编译时间
//如下增加了内存时间
int max_num(int a,int b)
{
return a > b ? a : b;
}
3.用内存时间换内存空间?
C语言:
(inline(只能修饰函数C99)内联函数:将函数体的代码段内嵌到函数调用的地方)
inline:将代码内嵌到函数调用的地方,省去了函数调用返回的过程,提高了程序运行效率;
C语言下只要声明为inline(内敛),一定会一内联的方式进行处理
C++升级:
(编译器会判断:循环,静态,递归,异常处理;包括这些都不会实现)
什么时候使用inline? 功能简单且代码短小,但频繁调用!
// 5 行代码以内吧,一定相信存在必定有用!!!!
使用注意事项:
调用inline内联函数之前必须声明或者定义该函数
void func(); //不声明直接使用func()会报错,必须提前声明
inline void func(){}
内联函数作用:
inline max_num(int a,int b)
{
return a > b ? a : b;
}
int main()
{
max_num(5,3);
max_num(3,4);
}
4.默认参数:
5.重载
函数重载条件?
注意事项:注意默认参数对重载条件的影响;
void add(int a,int b = 5){}//报错,出现二义性;
void add(int a){}
1.结构 的升级:
2.class vs struct
C++新的数据类型:class;基本上完全等同于结构体;
语法上唯一一个区别:class默认权限是private,struct默认权限是public;
叫法区别:
①class类,struct结构体;
②class定义的变量称之为对象,struct称之为变量;
③class里保存得变量称之为属性或者成员变量;保存的函数称之为方法;
#include
using namespace std;
struct Node
{
int num;
public:
void test()
{
cout << "func test" << endl;
}
};
struct Nodes : public Node
{
void test1()
{
cout << "func test1" << endl;
}
};
int main()
{
Node p;
p.test();
Nodes ps;
ps.test();
return 0;
}
#include
using namespace std;
class Student
{
public:
int mNum;
char mName[20];
int mage;
/*void eat()//类内实现,有可能被内联,消耗内存
{
cout << "eat" << endl;
)
*/
void eat();//外部实现需要声明
};
void Student::eat()
{
cout << "eat" << endl;
}
int main(void)
{
return 0;
}
举一个将类写进头文件的例子:
func.h
#pragma once//防止头文件重复包含
#include
using namespace std;
class Student
{
public:
void eat()
{
cout << "eat" << endl;
}
};
func.c
#include
#include "func4.h"
using namespace std;
int main(void)
{
Student p;
p.eat();
return 0;
}
1.介绍
std提供的标准字符串处理的类,class string;
特点:可变长(动态分配)(所以不用关心长度)
#include
#include
using namespace std;
int main(void)
{
//string的定义
string s = "hello world";//s叫做对象
string t = "hahhahaha";
string s2 = s + t;//strcat
string s3("hello world");// 构造函数初始化的方式
string s4(10,'a');
cout << s3 << endl;
cout << s2 << endl;
cout << s4 << endl;
return 0;
}
2.string 的属性
#include
#include
using namespace std;
int main(void)
{
string s("helloworld");
// 两种方法无区别测长度,但建议使用size();
cout << "size1=" << s.size() << endl;//no '\0'
cout << "size1=" << s.length() << endl;//no '\0'
return 0;
}
#include
#include
using namespace std;
int main(void)
{
string s("helloworld");
cout << "size1=" << s.size() << endl;//no '\0' //jianyi yong
cout << "size1=" << s.length() << endl;//no '\0'
cout << "is empty:" << s.empty() << endl;
//判断是否为空,empty()
cout << "capacity:" << s.capacity() << endl;
s.resize(100);
//resize重置大小,但未修改字符串
cout << "capacity:" << s.capacity() << endl;
s.resize(100,'a'); //memset
//重置大小并用 a 填充
cout << "capacity:" << s.capacity() << endl;
cout << s << endl;
string s1;
string s2 = "";
cout << "is empty:" << s1.empty() << endl;
cout << "is empty:" << s2.empty() << endl;
return 0;
}
3. string 输入输出
#include
#include
using namespace std;
int main(void)
{
string s;
//cin.getline(s,100);//报错,实参string 形参char,类型不兼容
//cin >> s; // 获取单词用这种
getline(cin,s);//获取句子用这种
cout << s << endl;
string src;
strcpy(src,s.c_str());
cout << s << endl;
return 0;
}
4.string 的遍历
#include
#include
using namespace std;
int main(void)
{
string s("hello world");
for(int i = 0;i
5.迭代器iterator:指针(内部指针)
三种:正向,反向:const
#include
#include
using namespace std;
int main(void)
{
string s("hello world");
//string :: iterator begin_it = s.begin();
//string :: iterator end_it = s.end();
for(auto it = s.begin();it != s.end();it++)
{
cout << *it << endl;
}
//string::const_iterator it = s.cbegin();
for(auto it = s.cbegin();it!=s.cend();it++)
{
cout << *it << endl;
}
//string::reverse_iterator it = s.rbegin();
for(auto it = s.rbegin();it != s.rend();it++)
{
cout << *it << endl;
}
return 0;
}
自学网站:https://en.cppreference.com/w/
6.string 赋值
7.string 连接
8.string 比较
9.string 类的子串(比较重要,可以借助手册网站)
10.string 交换
11.下面是最重要最常用的
查找:最好去官网看源码:
#include
#include
using namespace std;
int main(void)
{
string const s = "this is a string";
std::string::size_type n;
n = s.find('w');
cout << n << endl;
if(n == string::npos)
{
cout << "not found" << endl;
}
return 0;
}
查找总结:
str.find_first_of(查找目标字符串中任意一个字符在str里第一次出现的位置)
str.find_last_of(查找目标字符串中任意一个字符在str里最后一次出现的位置)
str.find_first_not_of(s)(str中第一个不是目标字符串任意一个字符的位置)
str.find_last_not_of(s)(str中最后一个不是目标字符串中任意一个字符的位置)
12.替换 自学
13.删除 迭代器
#include
#include
using namespace std;
int main(void)
{
string s = "abdcd";
//s.erase(1;
//s.erase(1,1);
auto it = s.erase(s.begin()+1);
cout << *it << endl;
//cout << s << endl;
return 0;
}
14.插入 自学
用迭代器!!!
15.容器, 删除
#include
#include
#include
using namespace std;
int main(void)
{
vector v1={1,2,3,4,5};
vector v2;
vector v3;
v3.push_back("hello");
//v3.push_back("world");
//bianli
/*for(auto it = v1.begin();it!=v1.end();it++)
{
cout << *it << endl;
}
*/
//shanchu
v1.erase(v1.begin()+1);
//cout << v3 << endl;
for(auto it = v1.begin();it!=v1.end();it++)
{
cout << *it << endl;
}
return 0;
}
字符串I am from shanghai 分别用 C/C++ 实现逆序打印 输出:shanghai from am i;
基于《面向对象的嵌入式软件开发》一书。
① 构造函数的名称必须和类的名称相同
② 构造函数不能有返回值,函数体中不能有return语句
③ 构造函数在定义对象时会自动执行,不需手动调用
注意事项:
1.构造函数的调用是强制性的,不调用是错误的;
2.创建对象时只有一个构造函数会被调用,而且构造函数只会在对象刚被创建时被调用。
3.如果用户没有定义构造函数,那么编译器会自动生成一个默认的无参构造函数,
只是这个构造函数的函数体是空的,也不执行任何操作;
4.如果没有定义的拷贝构造函数,编译器也会自动生成一个默认的拷贝构造函数,进行简单数据的赋值。
5.一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。
一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。
① 为了避免进行数据的拷贝,最好使用引用;
② 为了避免实参数据被修改,最好使用常引用;
① 析构函数没有参数,不能被重载
② 一个类只能有一个析构函数,要么用户自己定义,要么编译器自动生成一个默认的析构函数
③ 析构函数在对象被销毁时调用,而对象的销毁时机与它所在的内存区域有关;
注意事项
① 初始化列表要优先于当前对象的构造函数先执行;
② 子对象的初始化顺序和其在初始化列表的排列顺序无关,但和在类中的声明顺序有关,先声明的先初始化
③ 析构函数的调用顺序与构造函数相反;
④ 参数初始化表还有一个很重要的作用,那就是初始化const成员变量,
初始化const成员变量的唯一方法就是使用参数初始化表。
对于上述不足的点补充:
问:对象初始化列表什么时候使用?
https://blog.csdn.net/wy1550365215/article/details/77930637
问:一个对象所占内存大小如何计算呢?如下举例说明:
#include
using namespace std;
class Test
{
public:
Test(int a,int b)
{
m_a = a;
m_b = b;
}
void print()
{
cout << "a=" << m_a << ",b=" << m_b << endl;
}
static void printS()
{
cout << "c=" << m_c << endl;
}
private:
int m_a;
int m_b;
static int m_c;
};
int main(void)
{
printf("size=%d\n",sizeof(Test));
Test t1(1,2),t2(3,4);
t1.print();
t2.print();
return 0;
}
测试结果:
size=8 表明是两个整型字节数大小。
其实C++类对象中的成员变量和成员函数是分开存储的;
① 普通成员变量:存储在对象中,与struct变量具有相同的内存布局和字节对齐方式。
② 静态成员变量:存储在全局数据区中。
③ 成员函数:存储在代码段中。
计算大小时不是很好理解,比如:
问:很多对象共用一块代码,代码是如何区分具体的对象?
这里涉及编译器对类中成员函数的处理。类在C++内部是用结构体来实现的?
#include
using namespace std;
struct Test
{
int m_a;
int m_b;
};
static int m_c = 10;
void Test_init(Test* const p,int a,int b)
{
p->m_a = a;
p->m_b = b;
}
void Test_print(Test* const p)
{
cout << "a=" << p->m_a << ",b=" << p->m_b << endl;
}
void Test_printS()
{
cout << "c=" << m_c << endl;
}
int main(void)
{
Test t1;
Test_init(&t1,1,2);
Test t2;
Test_init(&t2,3,4);
Test_print(&t1);
Test_print(&t2);
return 0;
}
如上代码模拟编译器的内部处理,以帮助更好理解 类,并不是编译器内部的真正实现。
由以上的转换,可以看出:
① C++类对象中的成员变量和成员函数是分开存储的,C语言中的内存四区模型仍然有效。
② C++中类的普通成员函数都隐式地包含一个指向当前对象的常指针,
在上面代码的转换中就是指针p,通过这个常指针可以知道当前操作的是哪一个对象。
③ 静态成员函数,成员变量属于类
静态成员函数与普通成员函数的区别:
① 静态成员函数不包含指向具体对象的指针。
② 普通成员函数包含一个指向具体对象的指针。
类的普通成员函数都有一个指向当前对象的常指针,C++的关键字this就是那个指针
功能:代表当前操作对象。
1.友元函数:当前类的外部定义,不属于当前类的函数也可以在类中声明,但要在前面加关键字friend,这样就构成了友元函数。友元函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数。友元函数可以访问当前类中的所有成员,包括public,proteted,private等属性的成员;
如何将非成员函数声明为友元函数呢?
#include
using namespace std;
class Student
{
friend void show(Student *pstu);
public:
Student(char *name,int age,float score):m_name(name),m_age(age),m_score(score)
{
}
private:
char *m_name;
int m_age;
float m_score;
};
void show(Student *pstu)
{
cout << pstu->m_name << "year is " << pstu->m_age << ",score is " << pstu->m_score << endl;
}
int main()
{
Student stu("ming",15,90,6);
show(&stu);
Student *pstu = new Student("li",16,80.5);
show(pstu);
return 0;
}
而友元函数不能 直接 访问类的成员,必须借助对象;
2.友元类
有缘类中的所有成员函数都是另外一个类的友元函数。
3.友元函数的几点注意事项:
① 友元函数的声明与位置,public,private等都无关,可以在类的内部任意位置声明友元函数。
② 友元函数不是类的内部函数。
③ 友元的关系是单向的而不是双向的,如果声明了类B是类A的友元类,
不等于类A是类B的友元类,类A中的成员函数不能访问类B中的private成员。
④ 友元的关系不能传递,如果类B是类A的友元类,类C是类B的友元类,不等于类C是类A的友元类。
⑤ 友元函数会破坏类的封装性,不是迫不得已,尽量少用友元函数。
四种目前先学两种,static_cast,const_cast;其他了解看如下链接https://www.cnblogs.com/linuxAndMcu/p/10387829.html#_label0
① static_case(静态转换)
② const_cast(常量转换)
③ reinterpret_cast(不相关类型的转换)
④ dynamic_cast(动态转换)
使用场景:
- 基本数据类型之间转换,但安全性问题由程序员自己把握;
- 在有类型指针 与 void* 之间转换 (目前是遇到这种情况的时候强转)
- 用于 类 层次结构中 基类 和 派生类 之间 指针 或 引用 的转换
上行转换(子类--->父类) 安全;
下行转换(父类--->字类) 不安全; //由于没有动态类型检查,所以是不安全的;
问:什么是动态类型检查 ?C++如何实现 ? 使用场景 ?
出现了很多暂时看不懂的名词,先放一放。
https://blog.csdn.net/yuanzhangmei1/article/details/11962887
问:静态类型和动态类型有什么区别?
发生在不同语言的类型比较上,和现阶段学习的知识不符合,但可以了解一下
https://www.jianshu.com/p/bc492fcbf18f
② 使用特点:
#include
using namespace std;
class CBase //基类(父类)
{
};
class CDerived : public CBase //派生类(子类)
{
};
int main()
{
//1.使用static_cast在基本数据类型之间转换
float fval = 10.12;
int ival = static_cast(fval); // float-->int
cout << ival << endl; //out:10;
//2.使用static_cast在有类型指针与void* 之间转换
int* intp = &ival;
void* voidp = static_cast(intp); // int*--->void*
//cout << *voidp << endl; //error,voidp的大小未知
long* longp = static_cast(voidp);
cout << *longp << endl; // out:10
//3.用于类层次结构中基类和派生类之间指针或引用的转换
//上行转换(派生类--->基类)是安全的
CDerived* tCDerived1 = nullptr;
CBase* tCBase1 = static_cast(tCDerived1);
//下行转换(基类--->派生类)由于没有动态类型检查,所以是不安全的
CBase* tCBase2 = nullptr;
CDerived* tCDerived2 = static_cast(tCBase2);//不会报错 但是不安全
// 不能使用static_cast在有类型指针内转换
float* floatp = &fval; //10.12的addr
//int *intp1 = static_cast(floatp);
//error,不能使用static_cast在有类型指针内转换
cout << *floatp << endl; //out : 10.12
return 0;
}
2.const_cast:(常量转换)
#include
using namespace std;
int main()
{
int value = 100;
const int* cpi = &value; // 定义一个常量指针
//*cpi = 200; // 不能通过常量指针修改值 常量指针可以修改指针
cout << *cpi << endl;
// 1. 将常量指针转换为非常量指针,然后可以修改常量指针指向变量的值
int* pi = const_cast(cpi); //将cpi的类型从 const int* 强转成 int*
// const 对 const
*pi = 200;
cout << *pi << endl;
// 2. 将非常量指针转换为常量指针
const int* cpi2 = const_cast(pi); // *cpi2 = 300; //已经是常量指针
const int value1 = 500;
const int& c_value1 = value1; // 定义一个常量引用 //左值引用绑定左值
//如果能取地址 就是左值; 不能取地址就是右值;
// 3. 将常量引用转换为非常量引用
int& r_value1 = const_cast(c_value1);
// 4. 将非常量引用转换为常量引用
const int& c_value2 = const_cast(r_value1);
}
问:如何体现封装解决代码的维护性 ?
简答:保证代码的独立性,从而提高代码的维护性
问:独立性的好处? 封装如何保证独立?
简答:通过类的权限修饰符限制访问权限。pubulic,private,protect
特点:
①.自动调用(实例对象时,自动调用构造函数)
② 构造函数的函数名与类名一致;
③ 构造函数没有返回值
④ 构造函数可以重载;
⑤ 当类中无任何构造函数时,系统会默认生成一个无参的构造函数
但凡有一个构造函数,编译器都将不再生成构造函数。
构造函数的种类:
①- 无参构造函数;
②- 有参构造函数;
③- 拷贝构造函数;
形参是该类型的对象引用:若类里无自定义拷贝构造函数,系统会默认生成拷贝构造函数。
深拷贝和浅拷贝(默认生成 的拷贝都是浅拷贝)(多个成员指向了同一块空间)
(多个成员指向不同的空间)(只有类里有指针才会涉及到深拷贝和浅拷贝)
④- 移动拷贝构造函数;
解决对象拷贝的开销问题;尤其是临时对象;
对象移动:将该对象移动到目标对象的空间(获取目标对象的空间使用权)
匿名对象:https://www.cnblogs.com/cthon/p/9173472.html
//移动拷贝构造函数
Test(Test &&other) noexcept
{
cout << "move copy Test" << endl;
this->m_ptr = other.m_ptr;
other.m_ptr = nullptr;
}
⑤- 类型转换构造函数;
explicit:防止编译器发生隐式转换
补充:C++11 引入的两个关键字,delete default
例如
class A
{
public:
//A()=delete;//告诉编译器不生成默认无参构造函数
//A()=default;//告诉编译器自动生成无参构造函数
};
如何写好构造函数 ???
https://blog.csdn.net/farsight2009/article/details/4601396
特点:
- 无返回值
- 规定函数名:~类名
- 无参
- 不能重载
- 当对象离开所在作用域释放空间时先调用析构函数。
- 一个类只有一个
1.thisi指针:每实例化一个对象都有一个指向该对象的指针(this)
2.对象模型:每个对象都有自己独立的属性(成员变量)空间,共享代码空间(方法,函数);
3.this指针的作用:区别同一个类定义的不同实例
1.定义并初始化(先于构造函数),初始化效率高于构造函数初始化(不用发生函数调用)
2.什么情况下会使用初始化列表?https://blog.csdn.net/wy1550365215/article/details/77930637
自己写二代初始化列表例子: 因为学的时候函数末尾忘记加分号 一直报错没发现。
然后写了构造函数,发现根本不会调用构造函数。
#include
using namespace std;
class A
{
public:
int m_a, m_b;
A(int a, int b) :m_a(a), m_b(b) {};
void print()
{
cout << m_a << "," << m_b << endl;
}
};
int main(void)
{
A a(5, 6);
a.print(); //输出5,6 正确;
return 0;
}
引用成员的初始化示例:
//int& r_num 课上提问答错了,必须记住!
class B
{
public:
int B_num;
B b;
//int& r_num;//不注销报错,原因是左值引用只能绑定左值
B () : B_num(0),b(5)//,r_num(0)
{};
B(int num)
{
this->B_num = num;
}
};
//运算符重载函数:=
Test &operator=(const Test &other)
{
cout << "operator =" << endl;
int len = strlen(other.m_ptr);
this->m_ptr = new char[len + 1];
strcpy(m_ptr, other.m_ptr);
return *this;
}
在构建一个类的时候,有些成员函数必定会写:
构造函数,析构函数,运算符重载,set/get方法,普通成员函数,静态成员函数
所以可能用会用到关键字:static,const,mutable,
其中关键字 static 的作用:
用来声明静态变量。a.修饰局部变量——>会提升局部变量的生存周期
b.修饰全局变量——>作用域被限制——>被限制到当前的原文件中
c.修饰函数——>作用域被限制——>被限制到当前的原文件中
1.关键字 static
①.静态成员:属于类,不属于类的对象(不存在对象的空间中,但被所有对象共享)
注意事项:
不能在构造函数中初始化,只能在类外初始化
权限修饰符会影响静态成员
class Test
{
public:
static int m_num;
int m_count;
private:
//static int m_connt //错误,权限修饰符会影响静态成员
};
int Test::m_num=5; //类外初始化
int main()
{
Test t1;
cout << sizeof(t1) <
②.静态成员函数:属于类,不属于对象(被所有对象共享,没有this指针意味着不能访问非静态成员)
class Test
{
public:
static int m_num;
int m_count;
static void print()
{
//cout << m_count << endl;//错误,访问非静态常量
cout << m_num << nedl;//正确,只能访问静态成员
}
};
//普通成员方法的函数名不是函数的入口地址,无法作为参数传递;
//static修饰的方法的函数名就是函数的入口地址,可以作为参数传递;
2.问题:
c/c++中static关键字的作用 ?
C语言:
https://www.cnblogs.com/ustc-anmin/p/11239257.html
C++升级后:
https://www.cnblogs.com/songdanzju/p/7422380.html
友元的作用:提高程序运行效率(让类的非公有函数可以直接访问类的非公有成员,
省去了函数调用的过程)
友元的缺点:破坏了C++ 的封装性!
友元的种类:友元函数,友元类,友元成员函数
1 友元函数:
意义:是指某些虽然不是类成员却能够访问类的所有成员的函数。
形式: friend 类型名 友元函数名(形参表);
注意:
① 函数必须在类中说明,说明可以出现在类的任何地方,包括在 private 和 public 部分;
② 类外定义。
2 友元类:
友元类的所有成员函数都是另一个类的友元函数,
都可以访问另一个类中的隐藏信息.(包括私有成员和保护成员)
注意事项:
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。同样要看类中是否有相应的申明。
1,2 写个简单例子
/* 形参很烦
友元函数的使用
因为友元函数没有this指针,则参数要有三种情况:
1.要访问非static成员时,需要对象做参数;
2.要访问static成员或全局变量时,则不需要对象做参数;
3.如果做参数的对象是全局对象,则不需要对象做参数.
4.可以直接调用友元函数,不需要通过对象或指针
*/
class One
{
public:
//友元类的声明,表示 two索取one的内容
friend class two;
//该函数是友元函数的声明
friend void print(int num,One& a);
private:
int m_num;
};
//友元函数能访问到类中所有成员
void print(int num, One& a)
{
a.m_num = num;
cout << a.m_num << endl;
}
class Two
{
public:
};
int main(void)
{
One a;
print(1, a);
return 0;
}
3.友元成员函数
// 使类B中的成员函数成为类A的友元函数,
// 这样类B的该成员函数就可以访问类A的所有成员了。
// 一般的讲,必须先定义包含成员函数的类,才能将成员函数设为友元。
// 另一方面,不必预先声明类和非成员函数来将它们设为友元。
class A;
//当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。这是类A的声明
class B
{
public:
void set_show(int x, A& a); //该函数是类A的友元函数
};
class A
{
public:
friend void B::set_show(int x, A& a); //该函数是友元成员函数的声明
private:
int data;
void show() { cout << data << endl; }
};
void B::set_show(int x, A& a)
//只有在定义类A后才能定义该函数,毕竟,它被设为友元是为了访问类A的成员
{
a.data = x;
cout << a.data << endl;
}
int main(void)
{
class A a;
class B b;
b.set_show(1, a);
return 0;
}
这几个运算符不能进行重载 “::”, "?: “, “.”, " .*” , “sizeof”
简单实现运算符重载例子:
#include
#include
#include
using namespace std;
using p_func = void(*)(void);
class Test
{
public:
int m_num;
string m_s;
char *m_ptr;
Test()
{
m_ptr = new char[100];
}
Test(int num, string s, char *ptr) : m_num(num), m_s(s)
{
int len = strlen(ptr);
this->m_ptr = new char[len + 1];
strcpy(m_ptr, ptr);
}
~Test()
{
delete[] m_ptr;
}
static void print()
{
cout << "hello world" << endl;
}
Test &operator=(const Test &other)
{
if (this != &other)
{
this->m_num = other.m_num;
this->m_s = other.m_s;
int len = strlen(other.m_ptr);
this->m_ptr = new char[len + 1];
strcpy(m_ptr, other.m_ptr);
}
return *this;
}
bool operator>(const Test &t)
{
return this->m_num > t.m_num;
}
bool operator<(const Test &t)
{
return this->m_num < t.m_num;
}
friend ostream &operator<<(ostream &out, const Test &t)
{
out << "num = " << t.m_num << endl;
out << "s = " << t.m_s << endl;
out << "ptr = " << t.m_ptr;
return out;
}
friend istream &operator>>(istream &in, Test &t)
{
in >> t.m_num;
in >> t.m_s;
in >> t.m_ptr;
return in;
}
operator int()
{
return this->m_num;
}
operator char*()
{
return this->m_ptr;
}
operator p_func()
{
return print;
}
//i++ ,++i; //实现
//()函数运算符 -- 函数对象
};
int main()
{
string s = "hello world";
Test t1(1, s, "zhangsan");
Test t2;
t2.operator=(t1);
//cout << s << "hello" << endl;
//cout.operator<<(cout,t2);
//operator<<(cout,t2);
cout << t2 << "hello" << endl;
Test t3;
cin >> t3;
cout << t3 << endl;
if(t2 > t3)
{
cout << "t2 > t3" << endl;
}
int num = static_cast(t3);
int num2 = static_cast(t2);
cout << num << endl;
cout << num2 << endl;
string s2 = static_cast(t3);
char *ptr = static_cast(t2);
//cout << s2 << endl;
cout << ptr << endl;
p_func f = static_cast(t2);
f();
for(int i = 0; i < 5 ; ++i)
{
}
return 0;
}
1.组合定义:在新类里面创建原有类的对象,重复利用已有类的功能。(has-a关系)
2.继承定义:可以使用现有类的功能,
并且在无需重复编写原有类的情况下对原有类进行功能上的扩展。(is-a关系)
简单写一个实例表示:
class A
{
public:
private:
};
class B
{
public:
A a;//B里有A的对象,就是组合
private:
};
class C : public B //继承
{
public:
};
int main(void)
{
return 0;
}
3.问题:继承规则
有public、private、protected三种
(它们直接影响到派生类的成员、及其对象对基类成员访问的规则)
① public(公有继承):
继承时保持基类中各成员属性不变,并且基类中private成员被隐藏。
派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;
派生类的对象只能访问基类中的public成员。
② private(私有继承):
继承时基类中各成员属性均变为private,并且基类中private成员被隐藏。
派生类的成员也只能访问基类中的public/protected成员,而不能访问private成员;
派生类的对象不能访问基类中的任何的成员。
③ protected(保护性继承):
继承时基类中各成员属性均变为protected,并且基类中private成员被隐藏。
派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;
派生类的对象不能访问基类中的任何的成员。
父类私有 绝对不可被继承的子类所使用
4.什么时候用组合,什么时候用定义
目前简单用 has-a 包含关系 判断用组合,is-a 属于关系 判断用继承
https://blog.csdn.net/niuyisheng/article/details/9734921
心得:多态性还有动态绑定很好用,:多态性是指不同类的对象对同一消息的不同回应,
我脑海里出现了一个场景:
我妈喊吃饭的时候,我爸可以慢悠悠下楼,我却是不可以的(慢了会被我妈念叨)
要想在程序中实现多态(动态绑定)得几个条件:
1.弄明白类继承图
2.避免例子中Base d=b这种错误,要通过父类的指针或者引用调用虚函数
3.指针的指向对象不能弄乱。
父类和子类之间互相赋值的类型兼容原则,支持向上类型转换。
作用:不会发生类型变化(只有继承的情况下,且不支持向下类型转换)
意义:实现多态的前提条件
利用了虚函数,暂时不太懂,不过没事
注意事项:类外实现不再加virtual;如果担心函数遮蔽,就在虚函数名字后面添加 override ;
https://www.cnblogs.com/alinh/p/9636352.html
#include
using namespace std;
class A
{
public:
int m_a;
A(int a):m_a(a) //初始化列表
{
cout << "A()" << endl;
}
virtual void print() //虚函数
{
cout << "A" << endl;
}
virtual ~A()//析构函数
{
cout << "~A()" << endl;
}
};
class B : public A
{
public:
int m_b;
B(int b) : m_b(b),A(5)
{
cout << "B" << endl;
}
virtual void print() override
{
cout << "B" << endl;
}
~B()
{
cout << "~B()"<< endl;
}
};
class C : public A
{
public:
int m_c;
C(int c) : m_c(c),A(5) {}//初始化列表
virtual void print()
{
cout << "C" << endl;
}
~C(){}
};
class D : public A
{
public:
int m_d;
D(int d) : m_d(d),A(5){}
virtual void print()
{
cout << "D" << endl;
}
~D(){}
};
//同一接口,传递不同的实例,执行不同操作!
// 如果没有虚拟函数,没有函数遮蔽,测试函数要写的如下所示,很麻烦
// void test(A a)
// {
// a.print();
// }
// void test(B b)
// {
// b.print();
// }
// void test(C c)
// {
// c.print();
// }
void test(A &pa)
{
pa.print();
}
//同一接口,传递不同的实例,执行不同操作!
//多态的条件:1、继承 2、发生函数遮蔽 3、虚函数
//多态的发生时机:父类的指针或者是引用指向子对象;
//多态的意义:可以在任意地方用子类替换父类;
//多态的作用:提高代码的扩展性;
(添加新的功能时,不修改原来的代码,只添加新的代码(开闭原则))
//多态实现的情况下,父类的析构函数必须是虚析构函数?
如果不是,就不能正常释放子对象的空间?
int main()
{
//父类和子类之间互相赋值的类型兼容原则:支持向上类型转换
// A a(5);
// B b(10);
// C c(15);
// D d(20);
// test(a);
// test(b);
// test(c);
// test(d);
// A *pa = &a;
// B *pb = &b;
// C *pc = &c;
// a = static_cast(b);
//向上转型:不会发生类型变化;(只有继承) 意义:实现多态的前提条件
// a.print();
// a = c;
// a.print();
// //同一接口,传递不同的实例,执行不同操作!
// pa = pb;
// pa->print();
// pa = pc;
// pa->print();
// pb = pa;
//b = static_cast(a);//不支持向下类型转换(只有继承)
//B b(10);
A *pa = new B(10);
delete pa;
return 0;
}
PS:后续笔记会一直在本文补充。