目录
一、指针笔记
1、常量指针:
语法:【const int * p2 = &a;】 。指向的值不能改,指向可以改
2、指针常量:
语法:【int * const p1 = &a;】 指向不能改,指向的值可以改
3、Const 即修饰指针又修饰常量:
语法:【const int * const p3=&a;】指向、指向的值都不可以改
4、指针数组
语法【 返回值类型 (*数组名[ ]) 】 【黄色这里好像可有可无】(因为()与[ ]优先级相同,不括起来就相当于是元素返回值类型为指针的函数数组,所以要括起来)、(本质是数组,数组里存着指针)
5、数组指针
语法【int (*p1)[len]=arr;】//因为数组名就是地址,普通的指针就可以。(指向数组地址的指针,本质是指针)
6、指针函数
语法:【数据类型 * 函数名(参数表) { 函数体} 】(返回值是指针的函数,本质是函数。数据类型是返回指针的类型)
例:利用指针函数来返回局部变量的值:
int * func()
{
int * p = new int(10);//动态内存分配在堆区,想删用delete。
return p;//指针函数的返回值是指针
}
int main()
{
int * a =func();
cout<<*a;
return 0;
}
//结果就是10
7、函数指针
语法:【数据类型(*函数名)(int x)】(int x 可有可无)【没有带“体”,因为是指向函数,不用管函数定义】(是指向函数的指针,本质是个指针)、(注意:指向函数的指针没有++和—运算)
8、结构体指针
指针用 -> 来访问数据成员。例:p1->name;
二、引用的笔记
语法: 数据类型 & 别名 = 对象
三、动态开辟内存空间
语法 : new 数据类型
相当于开辟了一段存储这种数据类型变量(或者对象)的空间,要用一个指针去接收它,例如
int * p = new int(10);
动态开辟内存的数据存储再堆区,
delete p;//则删除堆区上的数据。
(delete [] p;//p是数组名。删除开辟的数组)
四、类
1、类的多态
(多态设好处是可读性强,想实现一些新功能可以在后面加,而不影响前面代码。)
动态多态【地址晚绑定】,(绑定地址)在运行阶段发生。
父类中(写了个虚函数)存储的其实是指针,叫虚函数(表)指针,指向一个虚函数表,表内存储的是父类中虚函数的地址。当子类重写父类的虚函数时,子类中的虚函数表 内部 会替换成 子类的虚函数地址(没重写则继承父类)。(重写指 函数返回值类型、形参列表、函数名 完全相同)
父类指针或引用指向子类对象的时候,因为接口是子类的对象,所以调用函数时会先在子类虚函数表中找这个函数的地址,找到则走子类重写的函数的地址。
例:
#include<iostream>
using namespace std;
//利用多态设计一个计算机类
class calculator
{
public:
virtual int getresult()
{
return 0;
}
int num_1;
int num_2;
};
class add :public calculator
{
public:
int getresult()
{
return num_1 + num_2;
}
};
class multiply : public calculator
{
public:
int getresult()
{
return num_1 * num_2;
}
};
void test01()
{
calculator* p = new add;
p->num_1 = 12;
p->num_2 = 10;
cout<< p->num_1 <<"+"<< p->num_2<<"="<< p->getresult()<<endl;
delete p;//释放堆区的数据
p = new multiply;//p仍是父类指针,然后指向一个新的子类对象
p->num_1 = 12;
p->num_2 = 10;
cout << p->num_1 << "*" << p->num_2 << "=" << p->getresult() << endl;
}
int main()
{
test01();
return 0;
}
一般父类中的虚函数都没什么作用,主要都是调用子类中重写的虚函数,所以可以直接把父类中的虚函数写成纯虚函数,语法:virtual 返回值类型 函数名 (参数列表) =0;
当类中有了纯虚函数,该类也称为纯虚函数,纯虚函数无法实例化对象(无论是堆区还是栈区),子类若不重写纯虚函数则也为纯虚函数。
例2:
#include<iostream>
using namespace std;
//利用多态输出一下网购的流程。 买、取快递、使用
class shopping
{
public:
virtual void buy() = 0;
virtual void take() = 0;
virtual void use() = 0;
void want_to_eat()
{
buy();
take();
use();
}
};
class water :public shopping
{
public:
void buy()
{
cout << "购买农夫山泉。" << endl;
}
void take()
{
cout << "下楼去取快递。" << endl;
}
void use()
{
cout << "开盖直接喝水。" << endl;
}
};
class ice_cream :public shopping
{
public:
void buy()
{
cout << "网上购买雪糕。" << endl;
}
void take()
{
cout << "下楼去取快递。" << endl;
}
void use()
{
cout << "放冰箱里冷冻,想吃拿出来吃" << endl;
}
};
void test01(shopping * abc)
{
abc->want_to_eat();
cout << "-----------------" << endl;
};
int main()
{
test01(new water);
test01(new ice_cream);
return 0;
}
2、类的构造函数跟析构函数
一般构造函数跟类同名,析构函数也同名,不过前要加~
例:
class cat
{
Public:
Cat()
{
Cout<<”构造函数调用<<endl;
}
~ cat ()
{
Cout<<”析构函数调用<<endl;
}
};
3、虚析构:
(引入虚析构是因为,先父类构造->子类构造,然后就跳过了子类析构,直接到父类析构了,造成了数据泄露)
用来解决父类指针释放子类对象时释放不干净的问题(子类中没有堆区的数据,则可以不写)
虚析构和纯虚析构的共性:
可以解决父类指针释放子类对象,且都需要具体的函数实现
虚析构和纯虚析构的区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
Virtual ~ 类名(){}
纯虚析构语法:
Virtual ~ 类名()=0;
类名::~类名(){};//需要写具体实现
五、模板
模板:将类形参化。
模板案例练习:
#include<iostream>
using namespace std;
//利用函数模板来实现数组元素升序。小的在前.
template<typename T>
void myswap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
//满足选择排序算法的就交换,但是数组元素交换时,因为数据类型不一样,所以要再用一次模板。
template<typename T>
void mysort(T arr[], int len)
{
//选择排序:
for (int i = 0; i < len; i++)
{
int min = i;
for (int j = i + 1; j < len; j++)
{
if (arr[min] > arr[j])
{
min = j;//更新最小值下标。
}
}
if (min != i)
{
myswap(arr[min], arr[i]);
}
}
//输出数组。
for (int i = 0; i < len; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
void test01()//字符数组排序
{
char chararr[] = "badcef";
int len = sizeof(chararr) / sizeof(char)-1;
mysort(chararr, len);
}
void test02()//整型数组排序
{
int intarr[] = { 1,4,3,2,5,6,8,7,9 };
int len = sizeof(intarr) / sizeof(int);
mysort(intarr, len);
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
普通函数与函数模板的区别:(一般用模板就不用普通函数)
1、如果函数模板和普通函数都可以实现,优先调用普通函数
2、可以通过空模板参数列表来强制调用函数模板:函数名<>(形参);
3、函数模板也可以发生重载
4、如果函数模板可以产生更好的匹配,优先调用函数模板。例:普通函数2个形参都为int型,函数模板为T类型,如果传入char类型的实参,尽管普通函数可以将char类型强制转换成int类型,但是编译器会直接将T推导成char,省去强制转换的步骤,调用函数模板。
利用具体化的模板,可以解决自定义类型的通用化:
template<> 数据类型 函数名 (自定义类型 &a, 自定义类型 &b)
六、深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作,将数据直接复制过来,地址也看做普通数据
深拷贝:在堆区重新申请空间,进行拷贝操作
浅拷贝在遇到堆区开辟,则会delete 2次指针,即同一块内存空间释放2次,非法操作,这时要用深拷贝:(自己写拷贝构造函数)person(const & p){ 其他数据正常等号赋值; 指针的话要 m_height = new int(*p.m_height); }
也即遇到类的成员有指针时,拷贝构造都要进行深拷贝。
总结:如果属性有在堆区开辟的。一定要自己提供(深)拷贝构造函数防止浅拷贝带来的问题。
七、运算符重载:
语法: 数据类型 operator需要重载的符号 (形参){}
例:person operator+ (person & p1){person temp; temp.num1=this->num1+p1.num1; return temp;}
实现运算符加号可以自定义数据类型的数据相加,如果想让返回值作为左值,加&:person & operator+(){}
函数的返回值想要作为左值进行操作,那么在函数声明时:数据类型 & 函数名 (){}
八、补充笔记:
vs里面使用不安全的函数修改“newc++file.cpp”这个文件,将define的内容输入进去,下次在其他.c文件打开的时候自动将该define补充进去。
比如#define _CRT_SECURE_NO_WARNINGS 1可以解决scan函数不安全的问题。