2022的愿望:反抗C字辈的打压。(2)

简介: 2022的愿望:反抗C字辈的打压。(2)

15. 结构体

结构体是属于用户自定义的数据类型,允许存储不同数据。

语法:struct 结构体名 {结构体成员};

通过结构体创建变量的三种方式:

  • struct 结构体名 变量名
  • struct 结构体名 变量名={成员1值,成员2值...}
  • 定义结构体时顺便创建变量。
struct Student{
    string name;
    int age;
    int score;
}s3;
int main() {
    // 法1
    struct Student s1;
    s1.name = "李四";
    s1.age = 50;
    s1.score = 100;
    cout<<s1.name<<" "<<s1.age<<" "<<s1.score<<endl;
    // 法2
    struct Student s2={"张三",18,100};
    cout<<s2.name<<" "<<s2.age<<" "<<s2.score<<endl;
    // 法3 在上面末尾定义
    s3.name = "王五";
    s3.age = 10;
    s3.score = 20;
    cout<<s3.name<<" "<<s3.age<<" "<<s3.score<<endl;
}

结构体数组

上面我们只是定义了一个一个的学生,实际上多个学生的信息我们是要放到一起的,

语法:struct 结构体名 数组名[个数] = {{}, {}, {}...}

struct Student{
    string name;
    int age;
    int score;
};
int main() {
    struct Student arr[5]={
            {"张三",18,100},
            {"李四",50,50},
            {"王五",12,30}
    }; //范围内访问超出元素个数的如arr[3] 字符串不显示 数字为0 
    arr[1].name = "李十四";
    cout<<arr[1].name<<"的分数为:"<<arr[1].score<<endl;
}

结构体指针

通过操作符 -> 可以通过结构体指针访问结构体属性,后面学的类对象也是。

记得声明结构体指针时是 (struct) 结构体名 *p=&元素/数组名  即声明xx结构体类型的指针变量。

int main() {
    struct Student s={"张三",18,100}; // Student s={"张三",18,100};
    struct Student *p = &s; //  Student *p = &s;
    cout<<p->name<<"的分数为:"<<p->score<<endl; // 张三的分数为:100
}

如果你写的是p.name 它会自动帮你转换成p->name,明白指针的话(*p).score也行。

结构体嵌套

struct Student{
    string name;
    int age;
    int score;
};
struct Teacher{
    int id;
    string name;
    int age;
    struct Student stu;
};
int main() {
    struct Student s1={"张三",18,95};
    struct Teacher t = {001,"李老师",80,s1};
    t.stu.name = "张三万";
    cout<<t.name<<"的学生"<<t.stu.name<<"的分数为:"<<t.stu.score<<endl; 
    // 李老师的学生张三万的分数为:95
}
struct Student{
    string name;
    int age;
    int score;
};
struct Teacher{
    int id;
    string name;
    int age;
    struct Student stu;
};
int main() {
    struct Student s1={"张三",18,95};
    struct Teacher t = {001,"李老师",80,s1};
    t.stu.name = "张三万";
    cout<<t.name<<"的学生"<<t.stu.name<<"的分数为:"<<t.stu.score<<endl; 
    // 李老师的学生张三万的分数为:95
}

当然也可以通过for循环给t.arr[i]赋值或者 t.arr[i].name等的赋值。

struct Student{
    string name;
    int age;
    int score;
};
struct Class{
    int id;
    struct Student arr[5];
};
struct Teacher{
    int id;
    string name;
    int age;
    struct Class c;
};
int main() {
    struct Student s1={"张三",18,95};
    struct Student s2={"张四",19,97};
    struct Student s3={"张五",20,100};
    struct Class c1={001,s1,s2,s3};
    struct Teacher t = {001,"李老师",80,c1};
    for(int i=0;i<3;i++){
        cout<<t.name<<"的"<<t.c.id<<"班"<<"学生"<<t.c.arr[i].name<<"的分数为:"<<t.c.arr[i].score<<endl;
    }
    /*
        李老师的1班学生张三的分数为:95
        李老师的1班学生张四的分数为:97
        李老师的1班学生张五的分数为:100
     */
}

一般用结构体指针接收结构体数组:

struct Student{
    string name;
    int age;
    int score;
};
struct Class{
    int id;
    struct Student *stu;
};
int main() {
    struct Student s[3]={
            {"张五",20,100},
            {"张四",19,97},
            {"张五",20,100}
            };
    struct Class c1={001,s};
    for(int i=0;i<3;i++){
        cout<<c1.id<<"班的"<<"学生"<<c1.stu[i].name<<"的分数为:"<<c1.stu[i].score<<endl;
    }
    /*
        1班的学生张五的分数为:100
        1班的学生张四的分数为:97
        1班的学生张五的分数为:100
     */
}

这样只开辟一个指针变量的大小,用别人家地址存放的东西,节省空间不是吗。

结构体做函数参数

值传递

struct Student{
    string name;
    int age;
    int score;
};
void details(Student stu){ // struct Student stu
    cout<< stu.name<<","<<stu.age<<","<<stu.score<<endl;
    stu.name="xxxxxx";
}
// 值传递
int main() {
    struct Student s1={"张五",20,100};
    details(s1); // 张五,20,100
}

地址传递 节省空间

void details(Student *stu){
    // 这个时候只能用箭头
    cout<< stu->name<<","<<stu->age<<","<<stu->score<<endl;
    // cout << (*stu).name << "," << (*stu).age << "," << (*stu).score << endl; 这样也行
    stu->name="xxxxxx";
}
// 地址传递
int main() {
    struct Student s1={"张五",20,100};
    details(&s1);
    cout<< s1.name<<","<<s1.age<<","<<s1.score<<endl; // xxxxxx,20,100
}

地址传递数组(结构体数组也是数组,只是内部元素不同)

void details(Student *stu) { // 指针指向这个数组第一个
    for (int i = 0; i < 3; i++) {
        // 这个时候不能用箭头
        cout << stu[i].name << "," << stu[i].age << "," << stu[i].score << endl;
        stu[i].name = "xxxxxx";
    }
}
int main() {
    struct Student s[3] = {
            {"张五", 20, 100},
            {"张四", 19, 97},
            {"张五", 20, 100}
    };
    details(s); // 张五,20,100  再强调一下虽然s和&s cout出来的值一样,这里也不能写&s
    for(int i = 0; i < 3; i++) {
        cout << s[i].name << "," << s[i].age << "," << s[i].score << endl;
    }
}
/*
张五,20,100
张四,19,97
张五,20,100
xxxxxx,20,100
xxxxxx,19,97
xxxxxx,20,100
*/

结构体作为返回值

struct Student {
  int id;
  string name;
};
Student foo() {
  Student s = { 99,"xx" };
  return s;
}
int main() {
  Student p = foo();
  cout << p.name << endl;
}

愿意指针引用也行,但没必要,不做其他声明也只能用一次显然不好。后面讲类对象的时候会练,这里先略。

结构体中const应用场景

将函数中的形参改为指针,可以减少内存空间,而且不会复制新的副本出来。(指针只占8个字节,而如果是重新形参Student stu接收,要开辟该结构体一样大小的空间。)此时也就是所谓的地址传递,形参改变会改变实参。

为了防止误操作,可以加个const。即我们前面讲的const修饰指针(指向的值不可以变)。

这一看到这里飘红了。

当然这样也不对

当然这样就行了

要明白在数组那讲的const修饰指针修饰的是什么地方。前两个是地址对应的值,后面那个是地址。

二、C++进阶

1. 内存分区模型

C++程序在执行时,将内存大方向划分为4个区域:


代码区:存放函数体的二进制代码,又操作系统进行管理的(程序运行前)

全局区:存放全局变量和静态变量以及常量(程序运行前)

栈区:由编译器自动分配释放,存放函数的参数值,局部变量等(程序运行后)

堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。(程序运行后)

内存四区的意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。


程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域:


代码区:


       存放cpu执行的机器指令


       代码区是 共享 的,目的是对于频繁被执行的程序,只需在内存中有一份代码即可。


       代码区是 只读 的,使其只读的原因是防止程序意外地修改了它的指令。


全局区:


       全局变量和静态变量存放在此。


       全局区还包含了常量区,字符串常量和其他常量也存放在此。


       该区域地数据在程序结束后由操作系统释放。

通过打印地址可以发现,局部变量和局部常量地址相近;

全局变量、全局常量静态变量字符串常量地地址相近。

程序运行后

栈区:

  • 由编译器自动分配释放,存放函数的参数值,局部变量等。
  • 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

注意返回地址声明时要声明为指针类型。  

int* func() {
  int a = 10;
  return &a;
}
int main() {
  int* p = func();
  cout << *p << endl; //10
  cout << *p << endl; //2025425288
}

我们上面知道,编译器自动分配释放栈区数据,我们最后返回a的地址,此时func函数结束,里面的数据被释放。我们下面再通过*p去取的时候,该块内存以及没有访问权限或者访问出乱码了。


之所以第一次嫩更成功,是因为此时编译器给我们做了一次保留,担心是我们误操作。但第二次就不保留了。(可以static int a = 10; )

堆区:

  • 有程序员分配释放,若程序员不释放,程序结束之后有操作系统回收
  • 在C++中主要利用new在堆区中开辟内存

注意返回地址声明时要声明为指针类型。

int* func() {
//new返回该数据类型的指针,所以用指针接收 new的是float就用 float *接收
  int *a = new int(10); 
  return a; // a存的是堆区数据的地址,他返回给主函数的*p去接收。
}
int main() {
  int* p = func();  // 这里的int *p=a 相当于以前的 int a=10; int *p=&a;
  cout << *p << endl; // 10
  cout << *p << endl; // 10
  delete p;
  cout << *p << endl; // 引发异常
}
int* func() {
  int *arr = new int[10]; //数组中括号
  arr[0] = 99;
  return arr; // arr是数组首地址,他返回给主函数的*p去接收。
}
int main() {
  int* p = func();
  cout << p[0] << endl; // 99
  cout << p[0] << endl; // 99
  delete[] p; // 释放数组全部空间
}

这样又多了种定义变量和数组的方式 int *arr = new int[10];其中arr未数组名,也是它的地址。

这里new和delete的知识后面讲析构函数还会用到。

2. 引用

int main() {
  int a = 10;
  int& b = a; // int * const b = &a;
  cout << b << endl; // 10
  b = 99;  // *b = 99;
  cout <<a<<" "<< b << endl; // 99 99
}

这个怎么理解呢,以前存放10的这块地址叫a,以后也叫b了。即此时a,b都是变量,10的名字。

注意,如果写成了int b = &a;会报错(你怎么能让一个16进制的数字去赋值给一个int类型的变量呢?),别和前面学的指针 int * b = &a;搞混。

int main() {
  int a = 10;
  int *b = &a;
  cout << b << endl; // 00AFF804
  *b = 99;
  cout <<a<<" "<< *b << endl; // 99 99
}
  1. (开始定义时) 引用必须要初始化。(define、const也是 但const int &a去做函数参数可以不)
  2. 引用之后就不可以改变。
int main() {
  int a = 10,c=100;
  int &b = a;
  b = c; // 相当于执行 a=100或者说是b=100
  cout << b << endl; // 100 这是进行赋值,而不是更改引用。
  b = 99;
  cout <<a<<" "<< b << endl; // 99 99
}

可以用引用代替指针接收

void swap1(int& a, int& b) {
  int tem = a;
  a = b;
  b = tem;
}
void swap2(int* a, int* b) {
  int tem = *a;
  *a = *b;
  *b = tem;
}
int main() {
  int a = 3;
  int b = 5;
  swap1(a, b);
  cout <<a<<" "<< b << endl; // 5 3
  swap2(&a, &b);
  cout << a << " " << b << endl; // 3 5
}

引用做函数返回值

  1. 不要返回局部变量的引用
  2. 函数可以是左值
int& foo1() {
  int a = 10; // 局部变量 栈区 这个函数结束后释放
  return a;
}
int& foo2() {
  static int a = 99; // 静态变量 全局区 程序结束后释放
  return a;
}
int main() {
  int& a = foo1();
  cout << a << endl; // 10 编译器做了保留
  cout << a << endl; // 2038794632 内存已经被释放
  int& b = foo2();
  cout << b << endl; // 99
  cout << b << endl; // 99 
  foo2() = 10000; // 其实就是赋值 a=10000;
  cout << b << endl; // 10000 
}

注意

int& foo2() {
  static int a = 99; // 静态变量 全局区 程序结束后释放
  return a;
}

像这样用&声明名的函数,返回的内容还是a,foo2()结果依然是99,但此时你不能用int &类型的变量去接收,即int &b = foo2()是错的,即int &b = 99是错的,但const int &b = foo2()可以。 所以如果我像直接int &b = foo2()那就在函数声明时加上&,要么就 int b=foo2()一个最简单的接收函数返回值。

const修饰防止误操作

// int a = 99;
// int &b = a; //可以
// int &b = 5; 不能这样写
const int& b = 10; // 可以 不可修改
// const int *p = 5;  int* const p = 10; 不可以
void foo1(const int &a) {  // 加个const防止以后操作的时候不小心改变
  //用a接收而不是&a相当于 a = 1000; 形参 值传递了,不是引用了.
  cout << a << endl; 
}
int main() {
  int a = 10;
  foo1(a);
}

3. 函数高级

默认参数

如果函数声明时有默认参数,函数实现就不能有默认参数。二者只能有一个有。

void foo1(int a = 1, int b = 2);
void foo1(int a=1,int b =2) {
  cout << a <<" "<<b << endl;
}
int main() {
  int a = 10,b=20;
  foo1();
}

函数占位参数

// 目前阶段的展位参数我们还用不到取不到,以后会将。
// 占位参数也有默认参数  void foo1(int a,int=10) {}
void foo1(int a,int) {
  cout << a << endl;
}
int main() {
  int a = 10,b=20;
  foo1(a,b); // 此时必须传两个
}

函数重载

函数名可以相同,提高复用性

void foo(int a) {
  cout <<"重载函数1  "<< a << endl;
}
void foo(int a,int b) {
  cout << "重载函数2  " << a <<" "<< b << endl;
}
void foo(float a, float b) {
  cout << "重载函数3  " << a << " " << b << endl;
}
void foo(int a, float b) {
  cout << "重载函数4  " << a << " " << b << endl;
}
int main() {
  int a = 10,b=20;
  float c = 1.2, d = 3.14;
  foo(a);
  foo(a,b);
  foo(c,d);
  foo(a,d);
}
/*
重载函数1  10
重载函数2  10 20
重载函数3  1.2 3.14
重载函数4  10 3.14
*/

注:函数的返回类型返回值不能作为重载条件,看参数就行了。

函数重载注意事项

  1. 引用作为注意事项
  2. 有默认值的函数重载

引用作为注意事项

void foo(int &a) {
  cout <<"重载函数1  "<< a << endl;
}
void foo(const int &a) {
  cout << "重载函数2  " << a << endl;
}
int main() {
  int a = 10;
  foo(a); // 重载函数1  10
}

这种情况会走函数1,无论1 2 谁在前。

void foo(int &a) {
  cout <<"重载函数1  "<< a << endl;
}
void foo(const int &a) {
  cout << "重载函数2  " << a << endl;
}
int main() {
  int a = 10;
  foo(10); // 重载函数2  10
}

这种情况直接传10,走函数2。 因为走函数1不合法呗。

有默认值的函数重载

void foo(int a) {
  cout <<"重载函数1  "<< a << endl;
}
void foo(int a=99) {
  cout << "重载函数2  " << a << endl;
}
int main() {
  int a = 10;
  foo(a);
}

这样不可以 报错

void foo(int a, int b = 20) {
  cout <<"重载函数1  "<< a << endl;
}
void foo(int a=99) {
  cout << "重载函数2  " << a << endl;
}
int main() {
  int a = 10;
  foo(a);
    // foo(30,40) 可以
}

也不行 还报错,因为都能走。尽量避免把。

4. 类和对象

python面向对象_suic009的博客-CSDN博客

C++面向对象三大特征:封装继承多态。

语法 class 类名 {访问权限:属性/行为};

封装

设计一个圆类,显示周长。

#include<iostream>
#include<string.h>
using namespace std;
#define PI 3.1415926
class Circle {
public:
  int r;
  float calculate() {
    return 2 * PI * r;
  }
};
int main() {
  Circle c;
  c.r = 1;
  cout << c.calculate() << endl;// 6.28319
}

内部赋值/修改

设计一个学生类(小坑)

#include<iostream>
#include<string.h>
using namespace std;
#define PI 3.1415926
class Student {
public:
  int id;
  string name;
  void setS(int id1,string name1) { // 注意 这里不可以写一样的名字C++不认识,Py认识。
    id = id1;
    name = name1;
  }
  void show() {
    cout << id << " " << name << endl;
  }
};
int main() {
  Student s1;
  s1.id = 1;
  s1.name = "张三";
  s1.show(); // 1 张三
  Student s2;
  s2.setS(2, "李四"); // 如果写了 id=id name = name 下面就会输出乱码
  s2.show(); // 2 李四
}

公有、私有、保护权限


public            类内可以访问      类外可以访问          子类可以访问


private           类内可以访问      类外不可以访问      子类不可以访问


protected       类内可以访问      类外不可以访问      子类可以访问

class与struct

  • class默认权限是私有权限。
  • struct默认权限是公有权限。

成员属性私有化

这个还有有必要的,我们可以自己控制哪些可读哪些可写。

对于一些写的权限,我们可以进行校验,防止超出有效范围。

include<iostream>
#include<string.h>
using namespace std;
class People {
  int id;    // 只读
  string name; // 可读可写
  int age;   // 可读可写
public:
  People(int i=000, string n="未实名", int a=18) { // 这里以后讲 先写着玩玩。
    id = i;
    name = n;
    age = a;
  }
  void show() {
    cout << id <<" "<< name <<" "<< age << endl;
  }
  int show_id() {
    return id;
  }
  string show_name() {
    return name;
  }
  int show_age() {
    return age;
  }
  void set_name(string n) {
    name = n;
  }
  void set_age(int a) {
    if(a<0 || a>150){
      cout << "- - gun - -" << endl;
      return;
    }
    age = a;
  }
};
int main() {
  People p1(001,"张三",20);
  People p2(002,"李四");
  p1.show();
  p2.set_age(200);
  p2.show();
}
/*
1 张三 20
- - gun - -
2 李四 18
*/

   C++不能直接打印出 cout << p << endl;

   打印地址 cout << (int * )&p << endl;

对象的初始化和清理

C++利用构造函数和析构函数解决对象的初始化和清理问题。


注:这两个就算你不写,编译器自己调用自己空的。还有个拷贝构造函数也是默认的。也就是说有3个,默认构造函数、析构函数、拷贝构造函数是C++编译器自己添加的。


构造函数


       主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。


构造函数没有返回值也不写void

函数名称与类名相同

构造函数可以有参数,因此可以发生重载

程序在创建对象的时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数

主要作用在于对象销毁前系统的自动调用,执行一些清理工作。

  1. 析构函数没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include<iostream>
#include<string.h>
using namespace std;
class People {
public:
  People() {
    cout << "你好" << endl;
  }
  ~People()
  {
    cout << "拜拜" << endl;
  }
};
// 构造和析构都是必须实现的 如果我们不写 编译器会提供一个空实现的构造和析构
int main() {
  People p1; // 在栈上的数据 main函数执行完就会释放
}
/*
你好
拜拜
*/

构造函数的分类及调用

两种分类方式:

  • 按参数分为:有参构造和无参构造
  • 按类型分为:普通构造和拷贝构造
  People(const People &p) { // 引用接收 不能把它本身改了呀所以加个const
    // 这个p是另一个人对象
        num = p.num;
    cout << "拷贝构造函数" << endl;
  }

注意:构造函数(拷贝)用People &p接收参数可以,People p错(难道你自己写的构造函数构造你自己吗?)。

在其他函数中People p可以是值传递。

调用构造函数  

法1:括号法

#include<iostream>
#include<string.h>
using namespace std;
class People {
public:
    int num;
  People() {
    cout << "普通:无参构造/默认构造" << endl;
  }
  People(int a) {
    cout << "普通:有参构造" << endl;
  }
  People(const People &p) { // 不能把它本身改了呀
    // 这个p是另一个人对象
        num = p.num;
    cout << "拷贝构造函数" << endl;
  }
  ~People()
  {
    cout << "拜拜" << endl;
  }
};
int main() {
  People p1;    // 默认构造函数
  People p2(99);  // 有参构造函数
  People p3(p2);  // 拷贝构造函数 此时p3的姓名年龄等与p1一样
}
/*
普通:无参构造/默认构造
普通:有参构造
拷贝构造函数
拜拜
拜拜
拜拜
*/

注意:调用默认构造函数时,不要加()。python写多了真是不好改。

因为下面这行代码,编译器会认为是一个函数声明。

People p(); // 并不会有什么报错或者输出的对象或者初始化了声明对象

法2:显示法:

int main() {
  People p1;         // 默认构造函数
  People p2 = People(99);  // 有参构造函数
  People p3 = People(p2);  // 拷贝构造函数
}

注意:

  •  People(99); 匿名对象 当前程序结束后,系统会立即回收掉匿名对象。
  •  不要用拷贝构造函数初始化匿名对象  People(99) === People p3; 对象重定义。

法3:隐式转换法:

int main() {
  People p1;      // 默认构造函数
  People p2 = 99;   // 有参构造函数
  People p3 = p2 ;  // 拷贝构造函数
}

拷贝构造函数调用的三种情况

C++中拷贝构造函数调用时机通常有三种情况


使用一个已经创建完毕的对象来初始化一个新对象

值传递的方式给函数参数传值 (People p接收参数值传递相当于给形参赋值;ps任何形式拷贝构造函数中只能以People &p接收)

以值方式返回局部对象 (含有和接收return p返回值时赋值);

情况1:

int main() {
    People p1;            // 默认构造函数
    People p2(p1) ;      // 拷贝构造函数
}

情况2: 函数用 People p接收

#include<iostream>
#include<string.h>
using namespace std;
class People {
public:
  int num;
  People() {
    cout << "普通:无参构造/默认构造" << endl;
  }
  People(int a) {
    num = a;
    cout << "普通:有参构造" << endl;
  }
  People(const People &p) { 
    num = p.num;
    cout << "拷贝构造函数" << endl;
  }
  ~People()
  {
    cout << "拜拜" << endl;
  }
};
void foo(People p) { // 值传递
  cout << p.num << endl;
  p.num = 0;
  cout << p.num << endl;
}
int main() {
  People p1(99);
  cout << p1.num << endl;
  foo(p1);
  cout << p1.num << endl;
}
/*
普通:有参构造
99
拷贝构造函数
99
0
拜拜
99
拜拜
*/

情况3: 含有和接收return p返回值时赋值;

#include<iostream>
#include<string.h>
using namespace std;
class People {
public:
  int num;
  People() {
    cout << "普通:无参构造/默认构造" << endl;
  }
  People(int a) {
    num = a;
    cout << "普通:有参构造" << endl;
  }
  People(const People& p) {
    num = p.num;
    cout << "拷贝构造函数" << endl;
  }
  ~People()
  {
    cout << "拜拜" << endl;
  }
};
People foo() { // 注意是People  声明函数不止int float等
  People p(99);
  return p;
}
int main() {
  foo(); // 匿名对象 直接释放(直接析构)
  cout << "ok" << endl;
  //cout << p.num << endl;
}
/*
普通:有参构造
拷贝构造函数
拜拜
拜拜
ok
*/

在释放前执行的拷贝构造函数

#include<iostream>
#include<string.h>
using namespace std;
class People {
public:
  int num;
  People() {
    cout << "普通:无参构造/默认构造" << endl;
  }
  People(int a) {
    num = a;
    cout << "普通:有参构造" << endl;
  }
  People(const People &p) { 
    num = p.num;
    cout << "拷贝构造函数" << endl;
  }
  ~People()
  {
    cout << "拜拜" << endl;
  }
};
People foo() { 
  People p(99);
  return p;
}
int main() {
  People p = foo(); // foo()先拷贝构造函数生成匿名对象
  cout << p.num << endl;
}
/*
普通:有参构造
拷贝构造函数
拜拜
99
拜拜
*/
People foo() { 
  People p(99);
  return p;
}
int main() {
  People p = foo(); // 匿名对象 直接释放
  cout << "ok" << endl;
  //cout << p.num << endl;
}
/*
普通:有参构造
拷贝构造函数
拜拜
ok
拜拜
*/

别急 再玩一下:

(上面是值传递 )

引用返回

People &foo() {
  People p(99);
  return p;
}
int main() {
  People &p = foo();
    // 当然了 要是People p = foo(); 就调用拷贝构造函数了,与声明函数时People foo() 无异
  cout << p.num << endl;
}
/*
普通:有参构造
拜拜
99
*/

指针

People *foo() {
  People p(99);
  return &p;  // 这里只能&p 说明对象不像数组一样名字就是地址
}
int main() {
  People *p = foo();
  cout << (*p).num << endl; // 这里要加括号 不然报错执行顺序不一样
    // cout << p->num << endl; 正常应该这样写
}
/*
普通:有参构造
拜拜
99
*/

我们可以看到,对于情况3的三种情况:


第一种:People p = foo(); 用p去接收一个对象p`,相当于 People p = p`; 执行拷贝构造函数。

第二种:People &p = foo(); 这个是引用,相当于People &p = p`;给这个起了个别名,不执行拷贝构造函数。

第三种:People *p = foo(); 涉及知识点毕竟较多,上面注释都写了,不执行拷贝构造函数。

2、3种不进行对象与对象之间的赋值,不执行拷贝构造函数。


但是要注意,对于情况三的第2、3种,我这样写只能打印一次,因为说过很多遍了其在栈区用完就清理了,得到的地址没变,里面的值没了。

People* foo() {
  People p(99);
  cout << (int*)&p << endl;
  cout << (int)&p << endl;
  return &p;
}
int main() {
  People* p = foo();
  cout << (int*)&*p << endl;
  cout << (int)&p << endl;
}
/*
普通:有参构造
00BBFAC8
12319432
拜拜
00BBFAC8
12319672
*/

当然你也可以new一块或者static,不过学指针也引用的时候我也写了,不提倡。


所以return回一个对象结构体啥的,因为可能还要用到就别整花里胡哨的直接返回个值就行,当作参数传递给函数可以考虑指针引用节省空间,可以考虑加个const不让他变。


如果这些你都想到了,说明你前面没白学。


相关文章
|
存储 JavaScript 前端开发
2022的愿望:反抗C字辈的打压。(3)
2022的愿望:反抗C字辈的打压。(3)
2022的愿望:反抗C字辈的打压。(3)
|
存储 JavaScript Java
2022的愿望:反抗C字辈的打压。(1)
2022的愿望:反抗C字辈的打压。(1)
2022的愿望:反抗C字辈的打压。(1)
别做被大公司毁掉的年轻人
大公司和小公司各有各的问题,无论你在大公司还是小公司,都要懂得如何取舍,如何坚持,如何做自己。今天先来谈谈大公司,看看如何避免成为被大公司毁掉的年轻人。
3072 0
|
算法 Java
当前处境的思考
能愿意花时间停下来思考,需要感谢007。没有像007这样的外力促使自己写作,自己就一直处于拖延的状态,拖延的原因也许在于自己还没想清楚,自己在做什么?又准备做什么。
IT人士如何扛起工作生活两座大山
当我们年轻时,初入职场,意气风发,恨不能倾尽所有精力工作,奋发图强、建功立业。当我们有了家庭,发现我亦凡人,事业家庭想两手抓,却两难兼顾。后来,我们发现工作生活两顾本身就是一个幻想,我们做的,只有尽量使之平衡。
2284 0
|
Java C++
做决定,怎能让“自己”缺席
【来信】   贺老师,您好,我在一次有目的的搜索中发现了您给大二软件工程学生解答的问题。仔细阅读后,决定写信给您。
1391 0