二、C++语言初阶:类与对象

2023-11-05

2:类与对象

2.1 认识类与对象

  • 什么是类(class)?
    类(class)是类型(type),是用户自定义的类型。为什么不叫它type,因为借用Simula语言中的class关键字。
  • 为什么要有类?
    基于便利性的考虑,现实世界中物(object)通常被分为几种独立的分类。
  • 基本概念
概念 比喻
对象/实例 楼房
实例化 建造
图纸
  • 面向对象四大特征
特征 说明 类比
抽象 抽出具体事物的普遍性的本质 分门别类:鸟类、哺乳类、鱼类
封装 把数据与处理(函数)包在一起 通信录(增加、删除)
继承 数据与处理(函数)的传承 财富与绝技、混血儿(肤色/头发)、 两种语言
多态 同一个事物(函数)的多种形态 手机键盘数字与字母、 电脑键盘功能键

2.2. 类的定义与对象创建

2.2.1 类的定义:与struct相似(C++)

class 类名{
    成员变量成员函数声明
};

class定义最后的;一定不要忘记。

  • 构成
构成 作用
数据成员(data member)/成员变量/属性 对象内部数据和状态,只能在类定义中声明,可以在成员函数中直接调用。
成员函数/方法 对象相关的操作,可以在类内实现或类外实现。
  • 访问限定符
限定符 作用
private[默认] 私有
public 公开
protected 保护

注:实践中,成员变量多数情况使用private或者protected,成员函数多数情况使用public。通常,通过成员函数改变对象的成员变量。

  • 类定义与类实现分离
    1、头文件 – 声明
    方式:#pragma once或者#ifnde...#endif
    作用:防止头文件二次编译
    实例:

complex.h

#ifndef __COMPLEX_H
#define __COMPLEX_H

class Complex{
private:
    int real;
    int imag;
public:
    void SetReal(int r);
    void SetImag(int i);
    int GetReal();
    int GetImag();
    void Print();
    Complex Add(Complex c1);
};
Complex Add(Complex c1,Complex c2);
#endif //__COMPLEX_H

2、源文件 – 实现
引用头文件:#include <>(标准库函数)/#include ""(自定义/第三方函数)
注:在C++书籍中为了方便.h与.cpp不做分离,但是项目开发中,需要分开。

  • 对象做参数和返回值
Complex Add(Complex c1,Complex c2);
Complex Complex::Add(Complex c);
  • 实例:复数

complex.cpp

#include <iostream>
#include "complex.h" //引用头文件
using namespace std;
void Complex::SetReal(int r) {
    real = r;
}
void Complex::SetImag(int i) {
    imag = i;
}
int Complex::GetReal() {
    return real;
}
int Complex::GetImag() {
    return imag;
}
void Complex::Print() {
    cout << real << "+" << imag << "i" << endl;
}
Complex Complex::Add(Complex c1) {
    Complex res;
    //成员函数可以直接访问自身的成员变量
    //res.SetReal(real+c1.GetReal());
    //res.SetImag(imag+c1.GetImag());

    //成员函数可以直接访问自身的成员函数
    //res.SetReal(GetReal()+c1.GetReal());
    //res.SetImag(GetImag()+c1.GetImag());

    //在成员函数中,当前类作为参数传入时,对象可以直接访问自身的私有成员
    //res.SetReal(real+c1.real);
    //res.SetImag(imag+c1.imag);
    res.real = real + c1.GetReal();
    res.imag = imag + c1.GetImage();
    return res;
}
Complex Add(Complex c1,Complex c2) {
    Complex c;
    c.SetReal(c1.GetReal()+c2.GetReal());
    c.SetImag(c1.GetImag()+c2.GetImag());
    return c;
}

main.c

#include "complex.h"
int main(){
    Complex c;
    c.SetReal(3);
    c.SetImag(5);
    c.Print();

    Complex c2;
    c2.SetReal(6);
    c2.SetImag(8);
    c2.Print();

    Complex c3 = Add(c,c2);
    c3.Print();

    Complex c4 = c.Add(c2);
    c4.Print();
}

实例1:复数(未拆分)

#include <iostream>
using namespace std;
class Complex{
private:
    int real;
    int imag;
public:
    void SetReal(int r){
    	real = r;
    }
    void SetImag(int i){
   	imag = i;
    }
    int GetReal(){
    	return real;
    }
    int GetImag(){
        return imag;
    }
    void Print(){
    	cout << real << "+" << imag << "i" << endl;
    }
    Complex Add(Complex c1){
	Complex res;
	//成员函数可以直接访问自身的成员变量
	//res.SetReal(real+c1.GetReal());
	//res.SetImag(imag+c1.GetImag());

	//成员函数可以直接访问自身的成员函数
	//res.SetReal(GetReal()+c1.GetReal());
	//res.SetImag(GetImag()+c1.GetImag());

	//在成员函数中,当前类作为参数,该对象可以直接访问自身的私有成员(c1和当前类属于同一类型,且只能在成员函数中访问)
	res.SetReal(real+c1.real);
	res.SetImag(imag+c1.imag);
    	//res.real = real + c1.GetReal();
    	//res.imag = imag + c1.GetImage();
	return res;
    }
};
Complex Add(Complex c1,Complex c2){
    Complex c;
    c.SetReal(c1.GetReal()+c2.GetReal());
    c.SetImag(c1.GetImag()+c2.GetImag());
    return c;
}
int main(){
    Complex c;
    c.SetReal(3);
    c.SetImag(5);
    c.Print();

    Complex c2;
    c2.SetReal(6);
    c2.SetImag(8);
    c2.Print();

    Complex c3 = Add(c,c2);
    c3.Print();
   
    Complex c4 = c.Add(c2);
    c4.Print();
}

实例2:账单

#include <iostream>
#include <cstring>
using namespace std;
class Bill{
public:
    void SetName(const char* s){
        strcpy(name,s);
    }
    void SetNum(int n){
        num = n;
    }
    void SetPrice(float p){
    	price = p;
    }
    float GetTotalPrice(){
        return num*price;
    }
    void Print(){
        cout << name << '\t' << num << "\t¥" << price << '\t' << GetTotalPrice() << endl;
    }
    void Scan(){
        cin >> name >> num >> price;
    }
    int GetNum(){
    	return num;
    }
private:
    char name[20];
    int num;
    float price;
};
int main(){
    int n;
    cin >> n;
    Bill b[n];
    for(int i = 0;i < n;++i){
    	b[i].Scan();
    }
    cout << "商品\t数量\t单价\t小计" << endl;
    float sum(0);
    int num(0);
    for(int i = 0;i < n;++i){
	num += b[i].GetNum();
        sum += b[i].GetTotalPrice();
    	b[i].Print();
    }
    cout << "----------------------------------" << endl;
    cout << "共计" << num << "件\t\t\t¥" << sum << endl;
}

2.2.2 C++classstruct区别

  • 1、默认的访问控制不同
    structpublicclass默认是private
  • 2、struct可以使用花括号内的初始值列表{...}初始化,class不可以(C++98不可以,C++11可以)。
    注:
    (1)C++class可以有成员函数,而C不可以。
    (2)C++class可以使用访问控制关键字(public private protected),而C不可以。
    (3)C++class成员变量默认初始化为随机值(主要影响指针)。

2.2.3 对象创建/实例化

  • 直接创建 – 类作为类型定义变量 – 栈上创建

例如:
1、基本类型

//c
int a = 10;
int b(10);
//c++
int a(0);// 等价 int a = 0;
const float b(1.0);// 等价 const float b = 1.0;

2、类类型

// 定义类
class Demo{};
// 创建对象
int main(){
    Demo d; // 变量(命名对象)
    Demo(); // 匿名对象
}

基本语法:

类名 对象名;  // 调用默认构造函数
类名(); // 创建匿名对象
  • 动态创建 – 堆上创建

1、基本类型

int* p = new int;
delete p;
p = NULL;

2、类类型

// 定义类
class Demo{};
// 创建对象
int main(){
    Demo* d = new Demo;
    delete d;
    d = NULL;
}

基本语法:

类名* 对象指针 = new 类名;// 调用默认构造函数
delete 对象指针;

对象指针new可以为对象设置初始值,例如下面代码

int* p = new int(100);
cout << *p << endl;
  • 动态创建数组 – 堆上创建

1、基本类型

int* pa = new int[10];
delete pa;// 只释放p[0]
delete [] pa;// 释放全部数组

2、类类型

// 定义类
class Demo{};
// 创建对象
int main(){
    Demo* d = new Demo[10];
    delete [] d;
    d = NULL;
}

对象数组指针new不可以为对象设置初始值。

int* pa = new int[10](100); // error: array 'new' cannot have initialization arguments 

注意:C++除了特殊情况,很少直接使用malloc()/free()申请释放内存,取而代之的是new/delete。

2.3 this指针

作用域:类内部
特点:
1、类的一个自动生成、自动隐藏的私有成员
2、每个对象仅有一个this指针
3、当一个对象被创建时,this指针就存放指向对象数据的首地址
4、不是对象本身的一部分,不会影响sizeof(对象)的结果
:如果成员函数形参与成员变量同名,使用this->做为前缀区分。

2.4方法

2.4.1 构造函数

  • 语法:
类名(参数){
    函数体
}
  • 实例:
Complex():real(0),imag(0){}
  • 特点:
    1、在对象被创建时自动执行
    2、构造函数的函数名与类名相同
    3、没有返回值类型、也没有返回值
    4、可以有多个构造函数
  • 调用时机
    1、对象直接定义创建–构造函数不能被显式调用
    2、new动态创建
  • 默认构造函数
    类中没有显式的定义任何构造函数,编译器就会自动为该类型生成默认构造函数,默认构造函数没有参数。
  • 构造函数的三个作用
    1、给创建的对象建立一个标识符
    2、为对象数据成员开辟内存空间
    3、完成对象数据成员的初始化
  • 构造函数初始化成员列表

语法:

类名(参数):成员变量(参数){
  函数体
}

实例:

Complex(int r,int i):real(r),imag(i){}

作用:
初始化非静态成员变量
说明:
从概念上来讲,构造函数的执行可以分成两个阶段,初始化阶段计算阶段,初始化阶段先于计算阶段。

加入构造函数后,对象可以用以下方式定义并初始化:

#include "complex.h"
int main(){
    int n(10); //int n - 10;
    //Complex c;
    //c.SetReal(3);
    //c.SetImag(5);
    Complex C(3,5);
    c.Print();

    /*
    Complex c2;
    c2.SetReal(6);
    c2.SetImag(8);
    */
    Complex c2(6,8);
    c2.Print();

    Complex c3 = Add(c,c2);
    c3.Print();

    Complex c4 = c.Add(c2);
    c4.Print();
}
  • 必须使用初始化列表的情况:
    1、常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面。
    2、引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面。
    3、没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
    注:能使用初始化列表的时候尽量使用初始化列表

2.4.2 析构函数

  • 语法:
~类名(){
    函数体
}

1、析构函数的函数名与类名相同
2、函数名前必须有一个~
3、没有参数
4、没有返回值类型、也没有返回值
5、只能有一个析构函数

  • 调用时机
    对象离开作用域
    delete

  • 默认析构函数
    类中没有显式的定义析构函数,编译器就会自动为该类型生成默认析构函数

  • 作用
    释放对象所申请占有的资源

  • 实例:

#include <iostream>
using namespace std;
class Simple{
    int n;
public:
    Simple(int n):n(n){
        cout << "n:" << n << endl;
        this->n = n;
        cout << "n:" << n << endl;
    }
    ~Simple(){
        cout << "~Simple()" << endl;
    }
    void SetN(int n){   //void SetN(Simple* this,int num){
        this->n = n;    //    this->n = num;
    }                   //}
    int GetN(){
        return n;
    }
    void Print(){
        cout << this << ":" << n << endl;
    }
};
int main(){
    /*
    Simple s1;
    s1.SetN(1);  //SetN(&s1,1);
    cout << &s1 << endl;
    s1.Print();
    */
    Simple s(10);
}

2.4.3 引用

  • 语法:
const 类型名& 对象名/类型名& 对象名
  • 使用:
    与对象变量、基本类型变量一样
  • 实例1:
#include <iostream>
using namespace std;
void swap(int* a,int* b){
    int t = *a;
    *a = *b;
    *b = t;
}
void swap2(int& a,int& b){
    cout << "&a:" << &a << endl;
    cout << "&b:" << &b << endl;
    int t = a;
    a = b;
    b = t;
}
int main(){
    int n = 10;
    int* p = NULL;
    p = &n;
    cout << "*p:" << *p << endl;
    cout << "n:" << n << endl;
    *p = 20;
    cout << "n:" << n << endl;
    
    int& r = n;
    cout << "r=" << r << endl;
    r = 30;
    cout << "r:" << r << endl;
    cout << "n:" << n << endl;
    cout << "*p:" << *p << endl;
    //引用就是一个别名,n和r地址相同,对r操作相当于对n操作,也相当于对*p操作
    cout << "&r:" << &r << endl;
    cout << "&n:" << &n << endl;
    cout << "p:" << p << endl;

    int x = 10,y =20;
    cout << "x:" << x << "\t" << "y:" << y << endl;
    swap(&x,&y);
    cout << "x:" << x << "\t" << "y:" << y << endl;
    swap2(x,y);// int& a = x;int& b = y;
    cout << "x:" << x << "\t" << "y:" << y << endl;
    cout << "&x:" << &x << endl;
    cout << "&y:" << &y << endl;
}
*p:10
n:10
n:20
r=20
r:30
n:30
*p:30
&r:0x7ffca409a4ac
&n:0x7ffca409a4ac
p:0x7ffca409a4ac
x:10	y:20
x:20	y:10
&a:0x7ffca409a4a8
&b:0x7ffca409a4a4
x:10	y:20
&x:0x7ffca409a4a8
&y:0x7ffca409a4a4

引用其实就是一个别名,a与b代表的是相同的对象。

  • 何处使用引用
    1、函数参数列表
    2、函数返回值
    3、成员变量 – 对象初始化时,必须显示初始化的变量
  • 为何使用引用
    1、避免对象复制
    2、避免传递空指针
    3、使用方便
  • 引用作用
    取代指针
void Func(int* n){
    *n = 2;
}
void Func(int& n){
    n = 3;
    cout << &n << endl;
}
int main(){
    int a =1;
    cout << &a << endl;
    Func(&a);
    cout << a << endl;
    Func(a);
    cout << a << endl;
}
  • 函数的三种传参方式
    1、传值void Func(int n)
    2、传地址/指针void Func(int* n)
    3、传引用void Func(int& n)

  • 引用与指针的区别
    1、指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
    2、引用只能在定义时被初始化一次,之后不可变;指针可变;
    3、引用不能为空,指针可以为空;
    4、引用使用时无需解引用*,指针需要解引用;
    5、sizeof 引用得到的是所指向的变量/对象的大小,而sizeof 指针得到的是指针本身的大小;
    6、对于引用类型的成员变量,所属类的大小时按照指针大小计算,自身大小按照自身类型计算。

2.4.4 拷贝/复制构造函数

  • 语法:
类名(类名& 形参){ 
    函数体
}

类名(const 类名& 形参){
    函数体
}
  • 调用时机

1、手动调用

类名 对象名;  // 调用默认构造函数
类名 对象2 = 对象1;    // 调用复制构造函数
类名 对象3(对象1);     // 调用复制构造函数

2、自动调用
(1)一个对象作为函数参数,以值传递的方式传入函数体
(2)一个对象作为函数返回值,以值从函数返回
(3)一个对象拷贝构造,它的成员对象自动调用拷贝构造
(4)子对象拷贝构造父对象自动拷贝构造

  • 实例:分别采用C和C++实现栈的创建、出栈、入栈等操作

C语言写法stack.c

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int Element;
typedef struct Stack{
     Element* data;
     size_t size;
}Stack;

Stack* Create(){
    Stack* s = malloc(sizeof(Stack));
    s->data = NULL;
    s->size = 0;
    return s;

}
bool Push(Stack* s,Element e){
    if(NULL == s){
    	return false;
    }
    s->data = realloc(s->data,sizeof(Element)*(s->size+1));
    if(NULL == s->data) return false;
    s->data[s->size] = e;
    s->size++;
    return true;
}
Element* Top(Stack* s){
    if(NULL == s || NULL == s->data) return NULL;
    return &s->data[s->size-1];
}
bool Pop(Stack* s){
    if(NULL == s || NULL == s->data) return NULL;
    s->size--;
    s->data = realloc(s->data,sizeof(Element)*s->size);
    if(s->data != NULL) return true;
    else return false;
}
size_t GetSize(Stack* s){
    return s->size;
}
void Destory(Stack** s){
     if(NULL == *s || NULL == s) return;
     free((*s)->data);
     free(*s);
     *s = NULL;
}
int main(){
    Stack* s = Create();
    for(;;){
        Element e;
		int num = scanf("%d",&e);
		if(EOF == num) break;
		Push(s,e);
    }
    while(GetSize(s) != 0){
    	printf("%d ",*Top(s));
		Pop(s);
    }
    printf("\n");
    Destory(&s);
}

C++写法 stack.cpp

#include <iostream>
#include <cstring>
using namespace std;

typedef int Element;
class Stack {
    Element* data;
    size_t size;
public:
    // 构造函数
    Stack(){
    	cout << this << ":Stack constructor" << endl;
	data = NULL;
	size = 0;
    }
    // 拷贝构造函数
    Stack(const Stack& s){
    	cout << "Stack copy constructor" << endl;
	size = s.size;
	data = s.data;
    }
    // 析构函数
    ~Stack(){
    	cout << this << ":Stack destructor" << endl;
	delete [] data;
	data = NULL;
	size = 0;
    }
    bool Push(Element e) {
        Element* tmp = new Element[size+1];
		if(NULL == tmp) return false;
		memcpy(tmp,data,sizeof(Element)*size);
        tmp[size] = e;
		delete [] data;
		data = tmp;
		++size;
		return true;
    }
    Element* Top() {
        if(NULL == data) return NULL;
        return &data[size-1];
    }
    bool Pop() {
        if(NULL == data) return false;
        size--;
		Element* tmp = new Element[size];
		if(NULL == tmp) return false;
        memcpy(tmp,data,sizeof(Element)*size);
		delete [] data;
		data = tmp;
    	return true;
    }
    size_t GetSize() {
        return size;
    }
    void Destory() {
        delete [] data;
		data = NULL;
		size = 0;
    }
};
int main() {
    Stack s;
    for(;;) {
        Element e;
        if(!(cin >> e)) break;
        s.Push(e);
    }
    while(s.GetSize() != 0) {
        cout << *s.Top() << " ";
		s.Pop();
    }
    cout << endl;
    s.Destory();
    
    //Stack t = s; //调用拷贝构造函数
    //Stack t(s); //拷贝构造
    //Func(s);  //Stack t = s; //拷贝构造
    Stack t = Return(); //真实执行拷贝构造但是被编译器优化了
    /* 近似操作
    Stack ReturnVal;
    {
        Stack s;
        s.Push(1);
        s.Push(2);
        s.Push(3);
        ReturnVal = s; // 这里真实执行的拷贝构造
    }
    */
    //cout << "&t:" << &t << endl;
}
}

2.4.5 默认拷贝构造函数

  • 本质:
    内存拷贝
  • 作用:
    复制一个已经存在的对象
  • 实例1:
#include <iostream>
#include <string>

using namespace std;

class Test {
public:
  Test() {}
  Test(Test &t) { cout << "Test(Test &t)" << endl; }
  Test(Test const &t) { cout << "Test(Test const &t)" << endl; }
};

int main() {
  Test t1;
  Test t2 = t1;
  const Test t3;
  Test t4 = t3;
}
Test(Test &t)
Test(Test const &t)
  • 实例2:
#include <iostream>
#include <string>
// 禁用编译优化g++ test.cpp -fno-elide-constructors
using namespace std;

class Test {
public:
    Test();
    Test(const Test &t);
    ~Test();
//private:
//    Test(const Test &t);  //禁用拷贝构造函数
};
Test::Test() {
    cout << this  << ":constructor" << endl;
}
Test::Test(const Test &t) {
    cout << this << ":copy constructor" << endl;
}
Test::~Test() {
    cout << this << ":destructor" << endl;
}
Test Return() {
    Test t;
    return t;  //NRVO: Name Return Value Option
    //return Test(); //RVO: Return Value Option
}
int main() {
    cout << "1" << endl;
    {
        cout << "2" << endl;
        //Test t;
        //Test(); //匿名对象 临时对象 生存周期只在当前语句
        /*
        Test* p = new Test; //手动创建
        delete p;  //需要手动销毁
        p = NULL;
        */
        Test t1 = Return();
        cout << "3" << endl;
    }
    cout << "4" << endl;
}
1
2
0x7ffe3671285f:constructor
0x7ffe3671288f:copy constructor
0x7ffe3671285f:destructor
0x7ffe3671288e:copy constructor
0x7ffe3671288f:destructor
3
0x7ffe3671288e:destructor
4

2.5 赋值运算符重载函数

  • 语法
类名& operater=(const 类名& 形参){
  // 赋值操作
  return *this;
}

加上引用后可以有效的避免函数调用时对实参的拷贝,提高了程序的效率。

  • 调用时机:
    类对象进行赋值的时候
  • 作用:
    赋值
  • 拷贝构造函数与赋值操作符的区别
    1、拷贝构造函数:用一个已经存在的对象来初始化一个不曾存在的对象
    2、赋值操作符:当两个对象都已经存在
  • 实例1:
#include <iostream>
using namespace std;

class Simple{
public:
    Simple(){
    	cout << "construtor" << endl;
    }
    explicit Simple(int n){  //explicit关键字,禁用构造函数的默认转换
    	cout << "construtor " << n << endl;
    }
    Simple(const Simple& s){
    	cout << "copy construtor" << endl;
    }
    //赋值运算符重载
    Simple& operator=(const Simple& s){
    	cout << "assign" << endl;
		return *this; // 返回当前对象
    }
    ~Simple(){
    	cout << "destructor" << endl;
    }
    //禁用拷贝构造函数和赋值运算符重载的方法一:删除函数
    //Simple(const Simple& s) = delete; // 禁用拷贝构造函数的方法
    //Simple& operator=(const Simple& s) = delete; // 禁用赋值运算符重载的方法

//禁用拷贝构造函数和赋值运算符重载的方法二:加入私有
/* 
private:
    Simple(const Simple& s); // 禁用拷贝构造函数的方法
    Simple& operator=(const Simple& s); // 禁用赋值运算符重载的方法
*/
};
void Func(Simple s){}
int main(){
    Simple s;
    Simple t;
    Simple w;
    w = t = s;
    // t = s; //返回t
    // w = t; //返回w

    Simple n1(1);
    Simple n2(2); //构造函数加入关键字后,只能这么写
    //Simple n3 = 2; // 构造函数加入explicit后不支持这种写法
    //类似操作:
    //Simple temp(2);
    //Simple n3 = temp;

    //Func(3); // Simple s = 3; //explicit,禁用构造函数的默认转换
    Func(Simple(3));
}

实例2:

#include <iostream>
using namespace std;
class Test{
public:
    Test(int i){
    	cout << this << " constructor " << i << endl;
    }
    Test(const Test& t){
    	cout << this << " copy onstructor " << &t << endl;
    }
    //赋值运算符重载
    Test& operator=(const Test& t){
    	cout << this << " assign " << &t << endl;
	return *this;
    }
    ~Test(){
    	cout << this << " destructor " << endl;
    }
};
void Func(const Test& t){}
void Function(int a,int b = 1,int c = 2){
    cout << a << " "<< b << " " << c << endl; 
};
int main(){
    //Test a(1);
    //Test a = 1; //近似过程:Test(1);Test a = Test(1); //g++ test3.cpp -fno-elide-constructors可以看到构造过程
	
    //Func(1); // 近似:cosnt Test& t = Test(1);

    Test a(1);
    a = 2;
    Function(100);
    Function(100,200);
    Function(100,200,300);
}
0x7ffe4dd6974e constructor 1
0x7ffe4dd6974f constructor 2
0x7ffe4dd6974e assign 0x7ffe4dd6974f
0x7ffe4dd6974f destructor 
100 1 2
100 200 2
100 200 300
0x7ffe4dd6974e destructor 

2.6 深拷贝与浅拷贝

  • 浅拷贝:
    编译器默认生成的类实例间拷贝行为,对带有指针的类来说会引发 memory leak(内存泄漏)。
  • 深拷贝:
    用户定义的行为(实质是一种构造函数)。
  • 区别:
    浅拷贝:只拷贝指针地址
    深拷贝:重现分配堆内存,拷贝指针指向内容
    注:不涉及指针,空间分配等资源问题时,深浅拷贝无区别
  • 浅拷贝存在的问题:
    当类中存在指针或者动态的内存分配时,使用浅拷贝只会将那块内存的位置告知当前对象,并不会重新为新对象分配内存。当程序运行结束后,两个对象分别析构,此时这同一块内存将被释放两次。释放第二次时,析构对象找不到需要释放的内存,就会导致内存泄漏。

实例:

#include <iostream>
using namespace std;
class Demo{
public:
    Demo(int n){
        p = new int(n);
    }
    ~Demo(){
        cout << p << endl;
        delete p;
        p = NULL;
    }
private:
    int* p;
};
int main(){
    Demo a(10);
    Demo b = a;
}
0x13c6e70
0x13c6e70
free(): double free detected in tcache 2
Aborted (core dumped)

问题分析:运行结果可以看出,对象a的指针p和对象b的指针p指向同一块内存,程序结果时,同一内存被delete两次导致程序运行出错。

  • 解决方式:
    深拷贝,重新申请空间,让拷贝对象指向新空间
#include <iostream>
using namespace std;
class Demo{
public:
    Demo(int n){
        cout << "constructor" << endl;
        p = new int(n);
    }
    Demo(const Demo& d){
        cout << "deep copy" << endl;
        //创造新的空间,再进行copy操作
        p = new int;
        *p = *d.p;
    }
    ~Demo(){
        cout << p << endl;
        delete p;
        p = NULL;
    }
    int GetData(){
        return *p;
    }
private:
    int* p;
};
int main(){
    Demo a(10);
    Demo b = a;
    cout << a.GetData() << "\t" << b.GetData() << endl;
}
constructor
deep copy
10	10
0x23462a0
0x2346280
  • 最佳实践:
    三大定律(Rule of three / the Law of The Big Three / The Big Three)
    如果类中明确定义下列其中一个成员函数,那么必须连同其他二个成员函数编写至类内,即下列三个成员函数缺一不可:
    1、析构函数(destructor)
    2、复制构造函数(copy constructor)
    3、复制赋值运算符(copy assignment operator)

2.7 友元

  • 作用
    非成员函数访问类中的私有成员
  • 分类
    全局友元函数:将全局函数声明成友元函数
    友元成员函数:类的提前引用声明,将一个函数声明为多个类的友元函数
    友元类:将整个类声明为友元
  • 特点
    友元关系单向性
    友元关系不可传递
  • 实例:
#include <iostream>
using namespace std;
class Integer {
    //友元类
    friend class Friend;
    //友元函数,允许访问类的私有成员
    friend void Func(Integer& num);
private:
    int n;
public:
    Integer(int n):n(n) {}
    void Print() {
        cout << n << endl;
    }
};
class Friend {
public:
    void Func(Integer& num) {
        cout << num.n << endl;
    }
};
void Func(Integer& num) {
    cout << num.n << endl;
}
int main() {
    Integer num(10);
    num.Print();

    Func(num);

    Friend f;
    f.Func(num);
}

2.8 const限定符

2.8.1 本质

只读(read only)

2.8.2 const与变量/对象

const 类型 变量 = 初始值;
const 类型 对象;

例如:

const int size = 4;

现在比较前卫写法:

类型 const 变量 = 初始值;
类型 const 对象;

例如:

int const size = 4;
  • 定义时必须初始化
  • 全局作用域声明的const变量默认作用域是定义所在文件
  • const 类型参数可以接受const变量和非const变量
    const类型参数只能接受非const变量
    例如:const对象只能调用const成员函数
  • const与宏定义#define的区别
区别 const 宏定义#define
编译器处理方式 编译运行阶段使用 预处理阶段展开/替换
类型 有具体的类型 没有类型
安全检查 编译阶段会执行类型检查 不做任何类型检查
存储方式 分配内存 不分配内存

2.8.3 const与指针

类型 语法 作用
const指针 类型* const 变量 = 初始值; 指针指向地址不能改变
指向const对象的指针 const 类型* 变量 = 初始值; 类型 const* 变量 = 初始值; 指针指向对象不能改变
指向const对象的const指针 const 类型* const 变量 = 初始值; 指针指向地址和对象不能改变

指向const对象的指针是使用最频繁的方式。

2.8.3 const与引用

类型 const &变量 = 初始值;const 类型& 变量 = 初始值;都是引用对象不能改变。

2.8.5 const与函数的参数和返回值

类型 语法 作用 说明
const参数 返回值类型 函数(const 类型 形参) 函数内部不能改变参数的值 这样的参数的输入值
const返回值 const 返回值类型 函数(形参列表) 函数的返回值不能改变 常用于字符串/指针
  • const成员变量
    不能在类声明中初始化const数据成员(C++11可以)
    const成员变量只能在类构造函数的初始化列表中初始化
class 类名{
public:
     类名(类型 形参):成员变量(形参){}
private:
     const 类型 成员变量;
}

应用:
const成员变量一般用于类定义后不可修改的信息,例如:学生学号。

  • const成员函数
    成员函数不能修改类中任何成员变量。一般写在成员函数的最后来修饰。

声明:

class 类名{
public:
    返回值类型 函数名(形参列表)const;
}

定义:

返回值类型 函数名(形参列表)const;

注:必须在成员函数的声明和定义后都加上const

const修饰位置 作用
变量 变量不可修改,通常用来替代#define
对象/实例 对象的成员变量不可修改,只能调用const成员函数
函数参数 参数不能在函数内部修改,只作为入参
函数返回值 返回的结果不能被修改,常用于字符串
成员变量 只能在初始化列表中初始化
成员函数 不改变成员变量

注:只要能够使用const,尽量使用const

  • 实例:
#include <iostream>
using namespace std;
class Test {
public:
    Test():n(100) {
        // n = 100 //初始化
    }
    //如果成员函数不修改(不想修改、不能修改)成语变量,最好在函数后加const
    void Print()const {
        cout << n << endl;
    }
    int GetNum() {
        return n;
    }
private:
    const int n;
};
//const 类型参数可以接受const变量和非const变量
//非const类型参数只能接受非const变量
void Print(const char* s) {
    cout << s << endl;
}
void Print(const int& n) {
    cout << n << endl;
}
int main() {
    //const int n = 10;
    int const n = 10;
    //n = 30; //不允许修改

    //const int m; //cosnt定义时必须初始化

    //常量指针 指针指向的值不能修改
    const int* p1;
    int const* p2;
    //指针常量 指针指向地址不能修改
    //int* const p3;

    const int& f1 = n;
    int const& f2 = n;
    //int& const f3;

    char* s = "abc";
    Print(s);
    const char* s1 = "ABC";
    Print(s1);

    int m = 10;
    Print(m);
    Print(n);
    Print(20);

    const Test t;
    t.Print();
    // cout << t.GetNum() << endl;   //const对象只能调用const成员函数
    Test t1;
    t1.Print();  //Test::Print(Test* this); // Test::Print(&t1);
    cout << t1.GetNum() << endl;   //const类型参数可以接受const型和非const型
}
abc
ABC
10
10
20
100
100
100

2.9 static限定符

  • 生存周期:
    整个程序的生存周期。
  • 作用域:
    属于类,不属于对象。
  • 语法:

声明:

class 类名{
    static 返回类型 函数(形参列表);
};

定义:

返回类型 类名::函数(形参列表){
    函数体;
}

调用:
1、通过类名(Class Name)调用

类名::函数(实参列表);

2、通过对象(Object)调用

对象.函数(实参列表);
  • 规则
    1、static只能用于类的声明中,定义不能标示为static
    2、非静态是可以访问静态的成员和函数
    3、静态成员函数不能访问普通成员
    4、静态成员函数可以设置private,public,protected访问权限
  • 禁忌
    1、静态成员函数不能访问非静态(非static)函数或者变量
    2、静态成员函数不能使用this关键字
    3、静态成员函数不能使用cv限定符(const与volatile)
    因为静态成员函数是属于类而不是某个对象。
    volatile是一个不常用的关键字,作用是改善编译器的优化能力。
  • 静态成员变量
    语法:
    1、在类定义中声明,但是在类实现中初始化。
    2、在声明时需要指定关键字static,但是在类外定义时不要指定static
    3、对象的大小不包含静态成员变量
    因为静态成员变量是属于类而不是某个对象。静态成员变量所有类的对象/实例共享。
static修饰位置 作用
变量 静态变量
函数 只源文件内部使用的函数
成员变量 对象共享变量
成员函数 类提供的函数,或者作为静态成员对象的接口

单例模式:使用静态成员变量和静态成员函数。

  • 实例1:
#include <iostream>
using namespace std;
class Test{
public:
    void Hello();
    static void World();
private:
    int n;
};
void Test::Hello(){
    cout << "Hello\t" << "n:" << n << endl;
}
//函数声明中加static,但是函数实现不能添加static
/* static */void Test::World() /* const */{  //静态成员函数不能添加const是因为函数本身没有传指针(const Test* this)
    cout << "World\t" << /*"n:" << n */<< endl; //不能访问普通成员
}
int main(){
    Test t;
    t.Hello(); //类似Hello(&t);
    //Test::Hello(); //错误
    //通过类名调用
    Test::World(); //类似World()
    //对象调用
    t.World(); //World()
}
Hello	n:0
World	
World	

实例2:

#include <iostream>
using namespace std;
class Test{
public:
    void Hello()const;
    static void World();
//private:
    static int n; //static属于类,不属于对象
};
//static只能用于类的声明中,定义不能标示为static。
int Test::n = 0; //类外初始化

void Test::Hello()const{  //类似void Test::Hello(const Test* this);
    cout << "Hello\t" << "n:" << n << endl;
}
//函数声明中加static,但是函数实现不能添加static
/* static */void Test::World() /* const */{  //不能添加const是因为函数本身没有传指针(const Test* this)
    cout << "World\t" << "n:" << n << endl; //不能访问普通(非static)成员
}
int main(){
    Test t;
    t.Hello(); //类似Hello(&t);

    //2种方法
    //通过类名调用
    Test::World(); //类似World()
    //对象调用
    t.World(); //World()

    cout << sizeof(t) << endl;
    Test s;
    s.n = 20;
    s.World();
}
Hello	n:0
World	n:0
World	n:0
1
World	n:20
  • 实例3:static属于类,不属于对象
    下面代码输出为()
#include <bits/stdc++.h>
using namespace std;
class Test{
public:
    static char x;
};
char Test::x='a';
int main(){
    Test exp1,exp2;
    cout<<exp1.x<<" ";
    exp1.x+=5;
    cout<<exp2.x <<endl;
}

A. f a
B. a f
C. a a
D. f f

  • 答案:
a f
  • 实例4:
#include <iostream>
using namespace std;
class Object{
public:
    Object():id(count++){
    	++live;
    }
    ~Object(){
    	--live;
    }
    static int GetCount(){
    	return count;
    }
    static int GetLive(){
    	return live;
    }
    void Print()const {
        cout << id << ":" << this << endl;
    }
private:
    const int id; //注意int和const int时候构造函数的区别
    static int count;
    static int live;   
};
int Object::count = 0;
int Object::live = 0;

int main(){
    Object o1;
    Object o2;
    Object o3;
    Object o4;

    Object(); //匿名对象,构造完即析构
    Object();
    Object();
    Object();

    o1.Print();
    o2.Print();
    o3.Print();
    o4.Print();

    cout << Object::GetCount() << endl; //累计构造8个对象
    cout << Object::GetLive() << endl; //析构4个,还剩4个

    cout << sizeof(o1) << endl;
    cout << sizeof(Object) << endl;
}
0:0x7ffe1b7759fc
1:0x7ffe1b7759f8
2:0x7ffe1b7759f4
3:0x7ffe1b7759f0
8
4
4
4

2.10 const static限定符

1、static constconst static修饰变量的效果一样
2、属于类,不属于对象
3、不能在构造函数中初始化
实例:

#include <iostream>
using namespace std;
class StaticConstTest{
public:
    void print(){
        cout << test1 << " " << test2 << endl;
    }
private:
    static const int test1 = 1;
    static const int test2;
};

/* static */ const int StaticConstTest::test2 = 2;

int main(){
    StaticConstTest sct;
    sct.print();
}
  • cosnt、static、const static对比
变量类型 声明位置
一般成员变量 在构造函数初始化列表中初始化
const成员常量 必须在构造函数初始化列表中初始化
static成员变量 必须在类外初始化
static const/const static成员变量 变量声明处或者类外初始化

注:static const/const static成员变量在类初始化必须是数字类型

2.11 内联函数

inline – 宏定义的接班人
在这里插入图片描述

  • 条件
    一般用在代码比较简单的函数
  • 语法
    1、关键字inline必须与函数实现/定义体放在一起才能使函数成为内联,将inline放在函数声明前面不起任何作用
    2、定义在类声明之中的成员函数将自动地成为内联函数
    3、通常内联函数定义在头文件中。
  • 慎用内联
    1、如果函数体内的代码比较长,使用内联将导致内存消耗代价较高
    2、如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大
    3、不要随便地将构造函数和析构函数的定义体放在类声明中
  • 本质
    内联函数的代码直接替换函数调用,省去函数调用的开销

2.12 运算符重载

  • 运算符重载主要有两种方式实现:

1、成员函数运算符重载

返回值类型 operator 运算符(参数){
      函数体
}

2、友元函数运算符重载

friend 返回值类型 operator 运算符(形参列表) { 
      函数体 
} 
No. 类型 运算符 成员函数 友元函数
1 双目算术运算符 + - * / % 类名 operator 运算符(const 类名&) const 类名 operator 运算符(const 类名&, const 类名&)
2 关系运算符 == != > >= < <= bool operator 运算符 (const 类名& ) const bool operator 运算符 (const 类名&,const 类名&)
3 双目逻辑运算符 && ¦¦ bool operator 运算符 (const 类名& ) const bool operator 运算符 (const 类名&,const 类名&)
4 单目逻辑运算符 ! bool operator !() const bool operator ! (const 类名&)
5 单目算术运算符 + - 类名 operator 运算符 () 类名 operator 运算符 (const 类名&)
6 双目位运算符 & ¦ 类名 operator 运算符 (const 类名& ) const 类名 operator 运算符 (const 类名& ,const 类名& )
7 单目位运算符 ~ 类名 operator ~ () 类名 operator ~ (类名&)
8 位移运算符 << >> 类名 operator 运算符 (int i) const 类名 operator 运算符 (const 类名&,int i)
9 前缀自增减运算符 ++ – 类名 operator 操作符 () 类名 operator 操作符 (类名&)
10 后缀自增减运算符 ++ – 类名 operator ++ (int) 类名 operator ++ (类名&,int)
11 复合赋值运算符 += -= *= /= %= &= ¦= ^= 类名& operator 运算符 (const 类名& ) 类名& operator += (类名&,const 类名&)
12 内存运算符 new delete 参见说明 参见说明
13 流运算符 >> << - 参见说明
14 类型转换符 数据类型 参见说明 -
15 其他运算符重载 = [] () -> 参见说明 -
  • 实例1:
#include <iostream>
#include <cstring>
using namespace std;

class String {
public:
    //构造函数
    String(const char* str) {
        if(NULL == str) {
            this->str = NULL;
            size = 0;
        } else {
            size = strlen(str)+1;
            this->str = new char[size];
            if(NULL == this->str) return;
            strcpy(this->str,str);
        }
    }
    //拷贝构造函数
    String(const String& s) {
        if(NULL == s.str) {
            this->str = NULL;
            size = 0;
        } else {
            size = s.size;
            str = new char[size];
            if(NULL == str) return;
            strcpy(str,s.str);
        }
    }
    //赋值运算符重载
    String& operator=(const String& s) {
        if(this == &s) return *this; //自我赋值判断
        if(NULL == s.str) {
            delete [] str;
            str = NULL;
            size = 0;
        } else {
            size = s.size;
            char* tmp = new char[size];
            if(NULL == tmp) return *this;
            //释放原有内存
            delete [] str;
            str = tmp;
            strcpy(str,s.str);
        }
        return *this;
    }
    String operator+(const String& s)const {
        char str[size+s.size+1] = {0};
        strcpy(str,this->str);
        strcat(str,s.str);
        return String(str);
    }
    bool operator==(const String& s)const {
        return strcmp(str,s.str) == 0;
    }
    bool operator!=(const String& s)const {
        //return strcmp(str,s.str) != 0;
        return !(*this == s); //调用上面定义的==判断两个对象是否相等
    }
    friend ostream& operator<<(ostream& os,const String& s);
    friend istream& operator>>(istream& is,const String& s);
    //析构函数
    ~String() {
        delete [] str;
        str = NULL;
        size = 0;
    }
    size_t GetSize() {
        return size;
    }
    char operator[](int i)const {
        return str[i];
    }
    char Get(int i)const {
        return str[i];
    }
    void Print() {
        cout << str << endl;
    }
private:
    char* str;
    size_t size;
};
ostream& operator<<(ostream& os,const String& s) {
    return os << s.str;
    //等价
    //os << s.str;
    //return os;
}
istream& operator>>(istream& is,const String& s) {
    // is >> s.str;
    return is;
}
int main() {
    String s("abc"); //调用构造
    s.Print();

    String t = s; //初始化创建对象(从无到有)调用拷贝构造
    t = s; //赋值 两个对象都存在   调用赋值运算符重载
    t.Print();
    t = t; //自我赋值
    s.Print();

    String n(NULL);
    String m = n;
    m = n;
    m.operator=(n); //等价m = n;

    const char* str = "def";
    String w = s+s+str+"123";
    w.Print();

    for(int i = 0; i < s.GetSize(); ++i) {
        //cout << s[i]; //s[i]=>s.operator[](i)
        cout << s.Get(i) << " ";
    }
    cout << endl;

    cout << w << endl;

    cout << (s == w) << "\t" << (s != w) << endl;
    String w2 = s+s+str+"123";
    cout << (w == w2) << "\t" << (w != w2) << endl;
}
abc
abc
abc
abcabcdef123
a b c  
abcabcdef123
0	1
1	0
  • 实例2:
#include <iostream>
using namespace std;

class Float {
public:
    Float(float value):value(value) {}
    /*
    Float operator+(const Float& f) const {
    	return Float(this->value + f.value);
    }
    */
    void Print() {
        cout << this->value << endl;
    }
    /*
    //成员函数写法
    bool operator==(const Float& f) const {
    	return abs(this->value - f.value) < 0.000001;
    }
    bool operator!=(const Float& f) const {
    	return !abs(*this == f); //使用等于运算符重载函数判断
    }
    */
    friend Float operator+(const Float& a,const Float& b);
    friend bool operator==(const Float& a,const Float& b);
    friend bool operator!=(const Float& a,const Float& b);
    friend istream& operator>>(istream& is,Float& f);
    friend ostream& operator<<(ostream& os,Float& f);

private:
    float value;
};

//友元函数写法
bool operator==(const Float& a,const Float& b)  {
    return abs(a.value - b.value) < 0.000001;
}
bool operator!=(const Float& a,const Float& b) {
    return !abs(a == b); //使用等于运算符重载函数判断
}
ostream& operator<<(ostream& os,Float& f) {
    os << f.value;
    return os;
}
istream& operator>>(istream& is,Float& f) {
    is >> f.value;
    return is;
}

Float operator+(const Float& a,const Float& b) {
    return Float(a.value + b.value);
}

int main() {
    Float f1(3.14);
    //Float f1 = 3.14; // 等价上面,过程类似于:Float f1 = Float(3.14);
    f1.Print();

    //成员函数f3 = f1.operator+(1);
    //友元函数f3 = operator+(f1,1);
    Float f2(1.3);
    // Float f3 = f1 + 1.3; // 成员函数可以用,友元函数可以用
    Float f3 = 1.3 + f1; // 成员函数不可以用,友元函数可以用
    f3.Print();

    Float f4(3.14);
    cout << (f1 == f4) << endl;
    cout << (f3 == f4) << endl;
    cout << (3.14 == f4) << endl;
    cout << (f4 == 3.14) << endl;
    //f1.operator==(f2); // 成员函数判断
    //operator==(f1,f2); // 友元函数判断

    cout << f1 << " " << f2 << endl;
    //等价过程:
    //operator<<(cout,f1);
    //cout << " ";
    //operator<<(cout,f2);
    //cout << endl;
    cin >> f1;
    f1.Print();
}
3.14
4.44
1
0
1
1
3.14 1.3
3.1415
3.1415
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

二、C++语言初阶:类与对象 的相关文章

  • 何时使用 C++ 私有继承而不是组合?

    你能给我一个具体的例子吗 什么时候使用私有继承优于组合 就我个人而言 我将使用组合而不是私有继承 但在某些情况下 使用私有继承可能是特定问题的最佳解决方案 正在阅读C faq http www parashift com c faq lit
  • 如何部署包含第三方 DLL 文件的 C# 应用程序?

    首先 我对部署了解不多 我希望我的问题有意义 我需要将 C 应用程序安装 部署到多个桌面 它需要一个第三方 DLL 文件 一个 C 库 lpsolve55 dll 对于那些感兴趣的人 它是一个免费的 MIP LP 求解器 请参阅 lpsol
  • 如何从 C# 调用 F# 类型扩展(静态成员函数)

    FSharp 代码的结构如下 我无法控制源代码 namespace FS
  • c 使用 lseek 以相反顺序复制文件

    我已经知道如何从一开始就将一个文件复制到另一个文件 但是我如何修改程序以按相反的顺序复制它 源文件应具有读取访问权限 目标文件应具有读写执行权限 我必须使用文件控制库 例如 FILE A File B should be ABCDEF FE
  • C# 实体框架我们应该使用 POCO.Id 还是仅使用 POCO 设置关系?

    我在服务方法中遇到一种情况 将 POCO 分配为另一个 POCO 的子对象无法按预期工作 我正在使用实体框架 4 public void ChangeOrderCurrency Currency currency order Currenc
  • 为什么我在 WinForms 列表框中得到“System.Data.DataRowView”而不是实际值?

    每当我运行代码并尝试查看highscore我在列表框中得到的只是System Data DataRowView 谁能明白为什么吗 Code MySqlConnection myConn new MySqlConnection connStr
  • 使用默认行为将模型绑定到接口

    我正在尝试将控制器操作绑定到接口 但仍保持默认的绑定行为 public class CoolClass ISomeInterface public DoSomething get set ISomeInterface public clas
  • += 运算符在 C++ 中是如何实现的?

    这是我一直在思考的一个问题 但从未找到任何资源来说明这个问题的答案 事实上它不仅是为了 也适用于它的兄弟姐妹 即 等等 当然不是 考虑这个例子 int a 5 a 4 this will make a 9 现在考虑等效表达式 a a 4 T
  • 从二进制文件读取字节到 long int

    我有两个问题 我有二进制文件的数据 我想使用 read 函数读取前 8 个字节以签署 long int 但我不能 你知道我该怎么做吗 如何直接读取一块数据到字符串中 我可以像所示那样阅读吗 前任 ifstream is is open te
  • 文件加密与解密问题

    我一直在尝试在 VC Express 2010 中加密和解密文件 我见过的所有教程和文档都需要两个FileStreams 来加密文件 一个用于读取未加密的版本 另一个用于加密 当我实际编写代码时 它不断抛出错误 告诉我它无法打开该文件 因为
  • List 或其他类型上的 string.Join

    我想将整数数组或列表转换为逗号分隔的字符串 如下所示 string myFunction List
  • 模板定义中的友元函数

    我的问题有点相关this https stackoverflow com questions 1297609 overloading friend operator for template class one 我想重载某些类的运算符 te
  • 按 Enter 继续

    这不起作用 string temp cout lt lt Press Enter to Continue cin gt gt temp cout lt lt Press Enter to Continue cin ignore 或更好 in
  • 如何使用“路径”查询 XDocument?

    我想查询一个XDocument给定路径的对象 例如 path to element I want 但我不知道如何继续 您可以使用以下方法System Xml XPath Extensions http msdn microsoft com
  • Apache LOG:子进程 pid xxxx 退出信号分段错误 (11)

    Apache PHP Mysql Linux 注意 子进程 pid 23145 退出信号分段错误 11 tmp 中可能存在 coredump 但 tmp下没有找到任何东西 我怎样才能找到错误 PHP 代码中函数的无限循环导致了此错误
  • Web API 2.0 使用 pascalcase 模型接收驼峰式命名的 JSON 数据

    我正在尝试对我的 Web API 进行 PUT 调用 我在 WebApiConfig cs 中设置了以下内容 以处理以驼峰形式将数据发送回我的 Web 项目 config Formatters JsonFormatter Serialize
  • 在两个点之间创建一条曲线,每个点都具有标准化向量

    因此 我需要一种写入方法来在两点之间创建一条曲线 每个点都有一个指向任意方向的归一化向量 我一直在尝试设计这样一种方法 但一直无法理解数学 在这里 由于一张图片胜过一千个文字 这就是我所需要的 在图中 矢量垂直于红线 我相信向量需要进行相同
  • boost::spirit::qi::语法和可变参数模板

    我在使用可变参数模板定义语法时面临一个问题 我首先定义一些包含在某些结构中的简单语法 例如纬度 经度 如下所示 include
  • execlp() 系统调用输出错误

    这个非常简单的例子exec 系统调用 在这里 我试图打电话execlp 两次 但是 我没有得到例外的输出 它仅显示当前目录的第一次调用的输出 include
  • 线程安全的有限大小队列,不使用锁

    我正在尝试编写一个主题队列 但遇到死锁和其他多线程问题 我想用Interlocked CompareExchange避免lock用法 但这段代码并没有按预期工作 它只是擦除整个队列 我在这里做错了什么 public class FixedS

随机推荐

  • 基于51单片机的心率计脉搏体温测量仪WIFI传输APP设计方案原理图

    系统的功能分析及体系结构设计 末尾附文件 系统功能分析 本系统采用STC89C52单片机 LCD1602液晶 脉搏传感器 温度传感器DS18b20 WIFI模块电路设计而成 1 LCD1602液晶第一行显示设计信息 第二行显示心率和温度 2
  • 嵌入式软件开发之程序分层(二)

    前言 该内容是工作一年来通过上网或其他方式不断搜索 实践 总结出来的嵌入式软件开发经验 本文仅适用于单片机的裸机开发 希望能帮到正在学习这方面的朋友 如有不好的地方 请多多见谅 在嵌入式软件开发过程中 在程序架构的搭建完成之后 为了提高项目
  • 深度学习速成(5)torch.nn.Module类

    nn Module 是PyTorch中的一个基础类 nn即是Neural Networks 用于构建神经网络模型 所有的神经网络模型都必须继承于nn Module类 并实现 forward 方法来定义模型的前向传播过程 nn Module类
  • nginx 配置静态网页

    nginx 配置静态网页 进入配置文件 etc nginx conf d default conf 配置的时候小伙伴们一定要记得先备份一份文件 这样方便后续出错恢复 server listen 端口号 server name IP loca
  • 解决Springboot GET请求参数过长的情况

    项目场景 使用Spring Boot进行项目开发 解决Springboot GET请求参数过长的情况 问题描述 报错信息 Springboot GET请求参数过长抛出异常 Request header is too large 的问题 现象
  • 华为OD机试 - 冠亚军排名,奖牌榜排名(Java)

    题目描述 2012伦敦奥运会即将到来 大家都非常关注奖牌榜的情况 现在我们假设奖牌榜的排名规则如下 首先gold medal数量多的排在前面 其次silver medal数量多的排在前面 然后bronze medal数量多的排在前面 若以上
  • 2022-12-27 使用lodash库实现两个非空对象的深拷贝并输出这两个对象的并集

    问题描述 遇到这样一个题 如下 const a fruits apple banana series apple C banana A B const b fruits banana orange animals pig series ba
  • ESP32S系列芯片通过EC20完成OTA统一流程

    uint32 t end size 0 最后一包的大小 uint16 t page number 0 总包数 uint16 t now number 0 当前写入页数 uint32 t bin addr 0 文件地址指针偏移量 uint16
  • DSMM数据安全概述

    数据安全生命周期分为采集 传输 存储 处理 交换 销毁几个阶段 其中数据处理阶段是整个周期的核心阶段 数据处理安全与否直接关系到整体数据安全 那么今天分享内容就是数据处理安全的相关要求和实现目标 DSMM是Data Security cap
  • URP学习--LitShader

    我们来看一下URP下的LitShader LitShader也是基于物理渲染的 很多方法和属性看过默认管线PBR代码的应该都会很熟悉 我们现在再过一遍 加深一下印象 同时疏通一下以前可能没有掌握的地方 先看Shader的Properties
  • mysql workbench 建库建表

    选择Create Schema建库 选择Create Table建表 改表名 上拉箭头选择更多 此处最好全屏 不然显示不全
  • UE5实现物体高亮描边效果(含UE相关源码浅析)

    文章目录 1 实现目标 2 实现过程 2 1 UE Editor中相关源码 2 2 深度值描边 2 3 半透明材质处理 2 4 遮挡处理 2 5 视口边缘处理 3 参考资料 1 实现目标 在UE5中实现物体边缘高亮效果 且在被遮挡时在边缘显
  • Android报Caused by: android.content.res.Resources$NotFoundException: String resource ID #0x0 .解决办法

    如果遇到这个问题 首先考虑以下原因 你是否在setText 方法中设置了一个int型变量 比如setText 10 这样Android系统就会主动去资源文件当中寻找 但是它不是一个资源文件ID 所以就会报出这个bug 解决办法 将int型业
  • 多维时序

    多维时序 MATLAB实现LSTM长短期记忆神经网络多变量时间序列预测 考虑历史特征的影响 多指标 多图输出 目录 多维时序 MATLAB实现LSTM长短期记忆神经网络多变量时间序列预测 考虑历史特征的影响 多指标 多图输出 效果一览 基本
  • linux - 文件利用率快满了 - mongo日志

    https zhuanlan zhihu com p 82430795 查看文件利用率 df h 文件夹大小 ls lh 逐级搜索大文件或目录 du sh cd 返回上一层 ps ef grep mongo 启动 mongodb bin m
  • 布尔值(python)

    while语句 格式 while 表达式 语句 逻辑 当程序执行到while语句时 首先计算表达式的值 如果表达式的值为假 那么结束整个while语句 如果为真 则先执行语句 再去计算表达式的值 如果表达式的值为假 那么结束整个while语
  • 从匿名函数到函数式编程

    什么是匿名函数 匿名函数是一种没有名称的函数 也被称为 lambda 函数或者函数字面量 和普通函数不同 匿名函数不需要在定义时指定函数名 通常是在需要的时候直接定义和使用 匿名函数通常被用作一次性的函数 例如作为其他函数的参数或者返回值
  • IDEA调试技巧

    在项目开发的时候很多小伙伴能非常熟练的使用IDEA编写代码 但是对于IDEA调试的技巧掌握的不是很好 只会F7 F8 F9等这些基本调试功能 而像一些复杂的调试场景就无从下手 如 在for循环中调试某个特定值 修改变量的运行值等 本文介绍了
  • Kubernetes 动态分配存储卷之 NFS-Subdir-External-Provisioner

    NFS Provisioner 已经停止维护 现在新的 NFS 动态分配器已经切换为 NFS Subdir External Provisioner 该组件也是一个自动配置卷程序 它使用现有的和已配置的 NFS 服务器来支持通过持久卷声明动
  • 二、C++语言初阶:类与对象

    2 类与对象 2 1 认识类与对象 什么是类 class 类 class 是类型 type 是用户自定义的类型 为什么不叫它type 因为借用Simula语言中的class关键字 为什么要有类 基于便利性的考虑 现实世界中物 object