C++编程笔记
C++编程小笔记
1、const简单使用
2、构造函数
3、this指针
4、friend友元
5、重载
6、继承
7、多态
8、文件读写
9、模板
10、异常
11、标准输入输出
12、回调函数
13、移动构造
14、静态成员
15、C++常用涉及模式
16、Cmake学习
17、C++20 - concept
18、using定义函数指针
1.const的应用
//栈区或静态存储区
void printStu(const Student *p) { //使用const修饰,使得传入参数只读
//p->name = "老王"; 锁定p,使其只读
cout << p->name << endl;
}
void printStu(const Student &p) {
cout<<p.name<<endl;
}
void myConst(){
int *const p1 = &a;//指针常量:指向不能改,值可以改
*p1 = 100;
const int *p2 = &b;//常量指针:值不能改,指向可改
p2 = &a;
}
class Stu{
mutable int name; //mutable修饰的关键字,在常函数和常对象中都可以修改
void getName()const{ //常函数,在类中不允许修改指针的指向和值
this->name="张三"; //常函数中,仅允许修改用mutable修饰的关键字
}
}
const Stu S; //常对象,仅能调用常函数
//仿函数
class MyCompare {//仿函数,指能行使函数功能的类
public:
//后面的const表示函数不会修改任何成员数据的值
bool operator()(const int v1,const int v2) const {
return v1 > v2;
}
};
2.参数列表构造函数
函数调用顺序:父构造,子构造,子析构,父析构
class Person {
int m_A, m_B, m_C;
Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c) {//初始化列表构造函数
}
};
C++在定义对象时,如果同时用=对其进行赋值,就相当于自动调用了相应的构造函数
#include <iostream>
using namespace std;
class my{
public:
my(){
cout<<"无参构造"<<endl;
}
my(const char * s){
cout<<"string构造函数"<<endl;
this->ss=s;
}
my(int a){
cout<<"int构造函数"<<endl;
this->aa=a;
}
my(const my &m){
cout<<"拷贝构造"<<endl;
this->ss=m.ss;
this->aa=m.aa;
}
my(const my &&m){
cout<<"移动构造"<<endl;
this->ss=move(m.ss);
this->aa=m.aa;
}
my&operator=(int a){
cout<<"operator="<<endl;
this->aa=a;
}
string ss;
int aa;
};
int main()
{
my a="123";
my b=1;
my c;
c=1;
return 0;
}
运行结果:
string构造函数
int构造函数
无参构造
operator=
Hello World
3.this指针
与Java的this指针功能相似
Persons &addAge(const Persons& p) { //传入只读变量引用
this->age += p.age;
return *this; //返回当前对象
}
this是指针常量,不可修改 //this=NULL 是不合法的
4.friend 友元
友元函数 类内申明类外实现
class Buliding{
public:
House H;
void visitBedRoom(){
cout<<H.bedRoom<<endl;//因为Builing是House的友元类,所以可以访问私有成员
}
}
class House {
friend void getHouse(const House &H);
friend class Building;//友元类,可以访问隐私成员
private:
string bedRoom;
public:
string bathRoom;
};
void getHouse(const House &H) { //全局友元函数
cout << H.bathRoom << endl << H.bedRoom << endl;
}
5.重载
//重载输出符
class myInt {
public:
myInt &operator++();
myInt(int _a) : A(_a) {
}
friend ostream &operator<<(ostream &os, myInt &my);
private:
int A;
};
ostream &operator<<(ostream &os, myInt &my) {
os << my.A;
return os;
}
myInt &myInt::operator++() {
this->A += 1;
return *this;
}
//重载
class MyStr{
public:
int id;
char *name;
MyStr& operator =(const MyStr& str)//赋值运算符
{
cout << "operator =" << endl;
if (this != &str) //二者不相等
{
if (name != NULL)
{
delete[] name;
}
this->id = str.id;
int len = strlen(str.name);
name = new char[len + 1];
strcpy_s(name, strlen(str.name) + 1, str.name);
}
return *this;
}
//通过函数重载运算符
Complex operator+(Complex &C) {
Complex X(this->a + C.getA(), this->b + C.getB());
return X;
}
T &operator[](int index) {
return this->P[index];
}
6.继承
父类中所有的非静态成员都会被继承成为子类的一部分,所以子类所占的大小位父类大小加上自身数据大小
6.1处理同名元素
cout << "Son下的 X = " << S.X << endl;
cout << "Base下的 X = " << S.Base::X << endl;//通过这种方式访问父类中的同名元素
//父类成员函数调用也是如此
//子类中访问父类同名成员
class Son : public Base {
public:
Son() {
X = 20;
Base::X=30;
}
int X;
};
- 静态成员也可以通过以上方式调用,还可以直接通过类名调用
- 若出现同名成员函数,则子类函数会隐藏父类函数,调用父类函数则需要加作用域
( 子类名 ( 对象 ) :: 父类名 ( 对象 ) :: 函数名 )- 使用虚继承可以解决菱形继承的问题
6.1菱形继承
7.多态
多态为了写代码尽量遵循开闭原则,对修改关闭,对扩展开放,方便扩展和维护
class Animal {
public:
virtual void Speak() {
cout << "动物叫唤" << endl;
}
};
class Cat : public Animal {
public :
void Speak() {
cout << "喵喵喵喵喵" << endl;
}
};
class Dog : public Animal {
public:
void Speak() {
cout << "汪汪汪汪汪" << endl;
}
};
void DoSpeak(Animal &A) {
A.Speak();
}
//使用
Cat C;
DoSpeak(C); //喵喵喵喵喵
Animal *A = new Dog();
A->Speak();
使用父类指针指向子类对象时,delete该对象不会执行子类的析构函数,有可能造成内存泄漏
为了解决这个问题,需要使用虚析构函数,即在父类析构函数前加上virtual关键字
8.文件读写
srand((unsigned) time(NULL));//随机数种子
rand() % 10000 + 10000
void test01() {
ofstream ofs;
string path = "/home/lhh/Project/C_C++/Test4_file/test.txt";
ofs.open(path, ios::out); //写文件
ofs << "姓名:张三" << endl << "年龄:18" << endl;
ofs.close();
}
void test02() {
ifstream ifs;
string path = "/home/lhh/Project/C_C++/Test4_file/test.txt";
ifs.open(path, ios::in);
if (!ifs.is_open()) {
cout << "文件无法打开" << endl;
return;
} else {
cout << "打开成功" << endl;
char buff1[1024], buff2[1024];
//方法一
// while (ifs >> buff1) { //逐行读
// cout << buff1 << endl; 当一行大于1024时就不行了
// }
cout << "---------------" << endl;
//方法二
// while (ifs.getline(buff2, sizeof(buff2))) {
// cout << buff2 << endl;
// }
// 方法三
// string str;
// while(getline(ifs,str)){
// cout<<str<<endl;
// }
char c;
while ((c = ifs.get()) != EOF) {//逐字节读取
cout << c;
}
ifs.close();
}
}
二进制文件读写写入和读取对象
//二进制写入
void test03() {
ofstream ofs;
ofs.open("/home/lhh/Project/C_C++/Test4_file/Person.txt", ios::out | ios::binary);
Person p[2] = {{"张三", 18, "男"},
{"李四", 20, "男"}};
ofs.write((const char *) p, sizeof(Person));
ofs.write((const char *) (p + 1), sizeof(Person));
ofs.close();
}
//二进制读取
void test04() {
ifstream ifs;
ifs.open("/home/lhh/Project/C_C++/Test4_file/Person.txt", ios::in | ios::binary);
if (!ifs.is_open()) {
cout << "读取失败" << endl;
return;
}
Person *p = new Person;
for (int i = 0; i < 2 && !ifs.eof(); ++i) {
ifs.read((char *) p, sizeof(Person));
cout << p->name << endl << p->age << endl << p->sex << endl;
}
delete p;
//cout << (p+1)->name << endl << (p+1)->age << endl << (p+1)->sex << endl;
ifs.close();
}
- String转为char数组的方法
string myStr = "dasffdas";
char S[myStr.length()+1];
memset(S,'\0',myStr.length()+1);
strncpy(S, &myStr[0], sizeof(myStr));//目标地址 源地址 复制字符数量
cout << S << endl;
使用流持续写入文件时,由于数据并不是直接进入文件,而是先放进缓冲区,大量写入时可能会导致缓冲区数据被覆盖导致数据丢失,所以需要flush保证数据被写入文件
#include<fstream>
int main(){
std::ofstream outfile("test.txt");
for(int n=0; n<100; ++n{
outfile<<n;
outfile.flush();
}
outfile.close();
return 0;
}
9.模板
//函数模板写法
template<typename T>
void Swap(T &S1,T &S2){
T temp = S1;
S1 = S2;
S2 = temp;
}
int main(){
int a = 10,b = 20;
float c = 1.1,d = 2.2;
//隐式转换
Swap(a , b);
//显示声明
Swap<float>(c , d);
}
//类模板
template<class T1,class T2>
class Person{
Person(T1 _name;T2 _age):name(_name),age(_age){//类内实现
};
T1 name;
T2 age;
void show();
}
//构造函数类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 _name,T2 _age):name(_name),age(_age){}
//成员函数类外实现
template<class T1,class T2>
void Person<T1,T2>::show(){
cout<<"name: "<<name<<'\t'<<"age: "<<age<<endl;
}
10.异常机制
float Div(int a, int b) {
if (0 == b) {
throw b; //抛出一个异常,可以是任何数据或者对象
}
return a / b;
}
void Div_use() {
try {
Div(10, 0);
} catch (int e) { //接收异常,要用对应类型去接受,可以抛出多个不同类型的异常,
//也可以接收多个不同类型的异常
cout << "除数e = " << e << endl;
} catch (...){ //捕获所有异常
cout<<"未知类型异常!"<<endl;
}
}
//--------------------------------------------------------------------------
void func() noexcept(false);//新写法,括号内写true就表示此函数不可
//抛出异常,如果抛出,程序不会崩溃,而是自动终止
void func() {
//函数实现
...
}
11.标准输入输出
//cin.get() //读入一个字符并返回它的值
//cin.get(一个参数) //读入一个字符并把它存储在ch
//cin.get(两个参数) //可以读取字符串
//cin.get(三个参数) //可以读字符串
//cin.getline()
//cin.ignore() //读取字符并忽略指定字符
//cin.peek() //检查下一个输入的字符,不会把字符从流中移除
//cin.putback() //返回一个字符给一个流
//cin.clear() //清空cin缓冲
void test(){
cout<<"输入字符或者数字"<<endl;
char ch;
cin.get(ch);//从缓冲区获取一个字符
if(ch>='0' && ch<='9'){
cin.putback(ch);
int numget;
cin>>number;
cout<<"输入了数字"
}
else{
char buf[256];
cin>>buf;
cout<<buf<<endl;
}
}
12.回调函数
//定义函数指针
typedef int(*callback)(int);
//定义函数体
int pin(int x) {
return x * x;
}
//实现回调
int getPinfang(int x, callback C) {
return C(x);
}
//使用
void test() {
getPinfang(10, pin);
}
13.移动构造
首先明确两个概念 左值和右值
- 左值:相当于一个盒子,代表内存空间
- 右值:一个临时变量,一个值,带表的就是一个值
int a = 10;
a 就是左值,10 就是右值
class myClass{
int *memory = nullptr; //当前类所占有的内存空间
myClass(myClass &&myclass){ //移动构造
this->memory = myclass.memory;
myclass.memory = nullptr; //移动构造就是直接将你的东西取过来归我所用,然后在剥夺你的使用权
}
};
14. 类的静态成员
静态成员变量必须在类中声明,在类外定义+初始化
class P{
public:
static int i;
}
int P::i=100;
静态成员直接通过类名::变量名
进行访问
cout<<"i = "<<P::i<<endl;
//赋值
P::i=20;
静态成员是所有类对象共享的
此外,还有静态成员函数
17.concept关键字
最简单的concept
案例
template <typename T>
concept integral = std::is_integral_v<T>;
int a;
intergral<a>
判断传入的数据类型是否为int,是的话返回true
还有一种用法就是使用requires
关键字
template<typename T>
concept printable=requires(T t){
t.empty();
};
class A {
public:
void empty() {
}
};
class B {
public:
};
cout << "A has " << printable<A> << std::endl;
cout << "B has " << printable<B> << std::endl;
运行结果
判断传入的类型能否执行requires内的内容,能正常执行则返回true
这里就是判断类内是否又成员函数empty()
,还可以写为
template<typename T>
concept printable=requires(T t){
//是否有empty函数
t.empty();
//是否有to_string函数且函数返回值为string
{t.to_string()}->std::same_as<string>;
};
class A {
public:
void empty() {
}
};
class B {
public:
};
cout << "A has " << printable<A> << std::endl;
cout << "B has " << printable<B> << std::endl;
C++11中可以使用decltype
+declval
判断
template<class T>
struct has_val {
private:
template<class U>
static auto Check(int) -> decltype(std::declval<U>().val, std::true_type());//如果U有val,则编译通过返回true_type
template<class U>
static std::false_type Check(...);
public:
enum {
value = std::is_same<decltype(Check<T>(0)), std::true_type>::value
};
};
class T;
int main(void){
if(has_val<T>::value){
cout<<"有val成员变量";
}else{
cout<<"无val成员变量";
}
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_18811413/article/details/104606673
————————————————
版权声明:本文为CSDN博主「鸟哥01」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sinat_18811413/article/details/104606673
18.using定义函数指针
void S(int a,int b);
using i_i = void(*)(int,int); //定义了一个函数指针,指向返回值为void,参数为int,int类型的函数
//
typedef (*i_i)(int,int); //等价于上一个函数指针的定义
i_i f1 = S;
f1(1,2); //调用
使用using关键字定义的函数指针,看着更加像一个赋值操作,便于理解
使用函数指针指向类的非静态重载成员函数
class GrilFriend : {
std::string m_name;
public:
explicit GrilFriend(std::string name);
void hungry();
void hungry(const string &str);
};
//这种方式可以直接区分两个重载的类成员函数
using hunger1 = void (GrilFriend::*)();
using hunger2 = void (GrilFriend::*)(const string &str);
hunger1 h1 = &GrilFriend::hungry; //这样指向的就是hunger的无参版本
hunger2 h2 = &GrilFriend::hungry;