C++类与对象

1. stack声明与定义

引入构造器实现 自定义 栈大小

// constructor构造器
// 1. 与类名相同,无返回值,被系统生成对象时自动调用,用于初始化
// 2. 可以有参数,构造器的重载,默认参数,重载和默认参数不同时存在,包含标配,为了实现对象的无参创建
// 3. 若未提供任何构造器,系统默认生成一个无参构造器。若提供,则不再生成默认构造器
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;

class Stack
{
public:
  Stack(int size = 1024);

  void init();
  bool isEmpty();
  bool isFull();
  char pop();
  void push(char c);

private:
  char *space;
  int top;
};
bool Stack::isEmpty()
{
  return top == 0;
};
bool Stack::isFull()
{
  return top == 1024;
};
char Stack::pop()
{
  return space[--top];
};
void Stack::push(char c)
{
  space[top++] = c;
};

int main()
{
  Stack st;      // 无参构造器,标配
  Stack st2(10); // 有参构造器
  for (char v = 'a'; !st.isFull() && v != 'z' + 1; v++)
  {
    st.push(v);
  }
  while (st.isEmpty())
    cout << st.pop() << endl;
  return 0;
}

2. 构造器

2.1. 定义及意义

class 类名
{
	类名(形式参数)
		构造体
}
class A 
{
	A(行参){}
}

在类对象创建时,自动调用,完成类对象的初始化。尤其是动态堆内存的申请
规则:
1. 在对象创建时自动调用,完成初始化相关工作
2. 无返回值,与类名同
3. 可以重载,可默认参数
4. 默认无参空体,一经实现,默认不复存在

Stack::Stack(int size) 
{
	space = new int[size];
	top = 0;
}

2.2. 参数初始化表

Stack::Stack(int size) 
	:space(new int[size], top(0))
{}
#include <iostream>
#include <string.h>
using namespace std;
class A
{
public:
  A(const char *ps)
      : name(ps), len(strlen(ps)) {}
  void dis() const
  {
    cout << len << endl;
  }

private:
  int len;
  string name;
};

int main()
{
  A a("hello");
  a.dis(); // 5
  return 0;
}

结论:

  1. 此格式只能用于类的构造函数
  2. 初始化列表中的初始化顺序,与声明顺序有关,与前后赋值顺序无关
  3. 必须用此格式来初始化非静态const数据成员
  4. 必须用此格式来初始化引用数据

3. 析构器(Destructor)

3.1. 对象销毁时期

  1. 栈对象离开其作用域
  2. 堆对象被手动delete

3.2. 析构器的定义及意义

class 类名
{
	~类名()
		析构体
}
class A 
{
	~A() {}
}

在类对象销毁时,自动调用,完成对象的销毁。尤其是类中已申请的堆内存的释放。
规则:

  1. 对象销毁时,自动调用,完成销毁的善后工作
  2. 无返回值,与类名相同,无参数,不可重载与默认参数
  3. 系统提供默认空析构器,一经实现,不复存在
Stack::~Stack()
{
	delete []space;
}

3.3. 小结

析构函数的作用,并不是删除对象,而是在对象销毁前完成一些清理工作

4. 析构与构造小结

class Stu
{
public:
  Stu()
  {
    name = new char[100];
  }
  ~Stu()
  {
    delete[] name;
  }

  char *name;
  int age;
};
int main()
{
  Stu *ps = new Stu;
  strcpy(ps->name, "wyb");
  delete ps;
}

5. 多文件编程

通常将类的声明,放到.h文件中去,将实现放到.cpp中去

6. 拷贝构造(Copy constructor)

6.1. 拷贝构造的定义及意义

由已存在的对象,创建新对象。也就是新对象不由构造器来构造,而是由拷贝构造器来完成。
拷贝构造器的格式:

class 类名
{
	类名(const 类名 & another)
		拷贝构造体
}
class A 
{
	A(const A & another)
	{}
}

规则:
1. 系统提供默认的拷贝构造器。一经实现,不复存在
2. 系统提供的是等位拷贝,也就是浅浅的拷贝
3. 要实现深拷贝,必须要自定义

6.2. 拷贝构造发生的时机

  1. 制作对象的副本
  2. 以对象作为参数和返回值

6.3. 深拷贝和浅拷贝

系统提供默认的拷贝构造器,一经定义就不再提供。但系统默认的拷贝构造器是浅拷贝,如果类中包含的数据元素全部在栈上,浅拷贝可以满足,但如果在堆上,则会发生多次析构行为

#include <iostream>
#include <string.h>
using namespace std;

class A 
{
public:
	A(int d, char *p):data(d)
	{
		pd = new char[strlen(p) + 1];
		strcpy(pd, p);
	}
	~A() 
	{
		delete[] pd;
	}
	A(const A &another) 
	{
		pd = new char[strlen(another.pd) + 1];
		strcpy(pd, another.pd);
	}
	void dis() 
	{
		cout << dat << endl;
		cout << pd << endl;
	}
private:
	int data;
	char *pd;
}
int main() 
{
	A a(20, "china");
	a.dis();
	A b(a);
	b.dis();
	A c = b;
	c.dis();
	return 0;
}

7. this指针

7.1. 意义

系统在创建对象时,为了带来方便,默认生成的指向当前对象的指针

7.2. 作用

  1. 避免构造器的入参与成员名相同
  2. 基于this指针的自身引用还被广泛地应用于支持多重串联调用的函数中,比如连续赋值
class Stu
{
public:
  /* Stu(string name, int age) : name(name), age(age)
  {
    cout << "this:" << this << endl; // 0x16b24ad98
  } */
  Stu(string name, int age)
  {
    this->name = name;
    this->age = age;
    cout << "this:" << this << endl; // 0x16b24ad98
  }
  void display()
  {
    cout << name << "-----" << age << endl; // wyb---17
  }
  Stu &growUp()
  {
    this->age++;
    return *this;
  }

private:
  string name;
  int age;
};
int main()
{
  cout << "Welcome" << endl;
  Stu s("wyb", 17);
  cout << "&s:" << &s << endl; // 0x16b24ad98
  s.display();
  s.growUp().growUp().growUp().growUp().display();

  return 0;
}

8. 赋值运算符重载(Operator=)

8.1. 发生的时机

用一个已有对象,给另外一个已有对象赋值。两个对象均已创建结束后,发生的赋值行为

8.2. 定义

类名
{
	类名 &operator=(const 类名 &源对象)
		拷贝体
}
class A
{
	A &operator=(const A &another)
	{
	    // 函数体
		return *this;
	}
}

8.3. 规则

1. 系统提供默认的赋值运算符重载,一经实现,不复存在
2. 系统提供的也是浅拷贝,会造成内存泄漏,重析构
3. 要实现深深的赋值,必须自定义
4. 自定义面临的三个问题:
1. 自赋值
2. 内存泄漏
3. 重析构
5. 返回引用,且不能用const修饰。a = b = c => (a + b) = c

main.cpp

#include <iostream>
#include "mystring.h"
using namespace std;
int main()
{
  string s6;
  s6 = s3;
  string s7;
  s7 = s5 = s3;
  cout << s6.c_str() << endl;
  myString ms6;
  myString ms7;
  ms6 = ms3;
  ms6.operator=(ms3);
  ms7 = ms5 = ms3;
  ms7.operator=(ms5.operator=(ms3));
  cout << ms6.c_str() << endl;
  cout << ms7.c_str() << endl;
}

mystring.h

#ifndef STRING_H
#define STRING_H
#include <stdio.h>

class myString
{
private:
  char *_str;

public:
  myString(const char *p = NULL); // 默认参数只能在声明处
  myString(const myString &other);
  myString &operator=(const myString &another);

  ~myString();
  char *c_str();
};
#endif

mystring.cpp

#include "mystring.h"
#include <string.h>

// 浅拷贝,会导致内存重析构,double free,在有些情况下(含有堆空间时),要自实现拷贝构造
myString::myString(const char *p)
{
  if (p == NULL)
  {
    _str = new char[1];
    *_str = '\0';
  }
  else
  {
    int len = strlen(p);
    _str = new char[len + 1];
    strcpy(_str, p);
  }
}

// 编译器提供默认(编译成功的原因)。一旦自定义,系统不再提供默认
// 默认赋值运算符重载也是一种等位赋值。浅拷贝,浅赋值
// 浅赋值,有可能会导致自身内存泄漏、内存发生重析构、自赋值
myString &myString::operator=(const myString &another)
{
  if (this == &another)
    return *this;
  delete[] this->_str;
  int len = strlen(another._str);
  this->_str = new char[len + 1];
  strcpy(this->_str, another._str);
  return *this;
}

/* myString::myString(const myString &other)
{
  _str = other._str; // 在同类之间,是没有隐私的
} */
myString::myString(const myString &other)
{
  int len = strlen(other._str);
  _str = new char[len + 1];
  strcpy(_str, other._str);
}

myString::~myString()
{
  delete[] _str;
}

char *myString::c_str()
{
  return _str;
}

9. 返回栈上引用与对象

9.1. c语言返回栈变量

返回的过程产生了"中间变量"作为纽带

int func()
{
  int a = 5;
  return a;
}
int main(void)
{
  int i = 3;
  i = func();
  cout << i << endl;
  return 0;
}

不管是返回指针还是返回值,return将return之后的值存到eax寄存器中,回到父函数再将返回的值赋给变量

9.2. c++返回栈对象

class A
{
public:
  A()
  {
    cout << this << " constructor" << endl;
  }
  A(const A &other)
  {
    cout << this << " cp constructor from" << &other << endl;
  }
  A &operator=(const A &other)
  {
    cout << this << " operator = " << &other << endl;
  }
  ~A()
  {
    cout << this << " destructor" << endl;
  }
};

9.2.1. 本质推演

a传值:发生拷贝

void foo(A a) {}
int main()
{
  A a;
  foo(a);
  /*
    0x16b14edbb constructor
    0x16b14edba cp constructor from 0x16b14edbb
    0x16b14edba destructor
    0x16b14edbb destructor
  */
  return 0;
}

b传引用,没有发生拷贝

void foo(A &a) {}
int main()
{
  A a;
  foo(a);
  /*
    0x16d9e2dbb constructor
    0x16d9e2dbb destructor
  */
  return 0;
}

c返回对象

A foo(A &a)
{
  return a;
}
int main()
{
  A a;
  foo(a);
  /*
    0x16f27edbb constructor
    0x16f27edba cp constructor from 0x16f27edbb
    0x16f27edba destructor
    0x16f27edbb destructor
  */
  return 0;
}

在main的栈上事先开辟了一个临时空间,把这个空间的地址隐式的转到foo函数栈上。然后,把a内的东西,拷贝到临时空间中。所以发生一次构造,一次拷贝,两次析构
测试:

A foo(A &a)
{
  cout << "in foo : " << (void *)&a << endl;
  return a;
}
int main()
{
  A a;
  A t = foo(a);
  cout << "in main: " << (void *)&t << endl;
  /*
    0x16f346dbb constructor
    in foo : 0x16f346dbb
    0x16f346dba cp constructor from 0x16f346dbb
    in main: 0x16f346dba
    0x16f346dba destructor
    0x16f346dbb destructor
  */
  return 0;
}

此时main函数中产生的临时空间,由t来取而代之。此时t的地址,与a的地址不同

A foo(A &a)
{
  cout << "in foo : " << (void *)&a << endl;
  return a;
}
int main()
{
  A a;
  A t;
  t = foo(a);
  cout << "in main: " << (void *)&t << endl;
  /*
    0x16b89edbb constructor
    0x16b89edba constructor
    in foo : 0x16b89edbb
    0x16b89edab cp constructor from 0x16b89edbb
    0x16b89edba operator = 0x16b89edab
  */
  return 0;
}

此时main栈上通过拷贝构造产生了中间变量,中间变量向t发生了赋值

9.2.2. 本质结论

栈上的对象是可以返回的,但不能返回栈上的引用(除非对象本身)
linux中做了更深层次的优化

 A foo()
{
  A b;
  cout << "in foo : " << (void *)&b << endl;
  return b;
}
int main()
{
  A t = foo();
  cout << "in main: " << (void *)&t << endl;
  /*
    0x16cf96dbb constructor
    in foo : 0x16cf96dbb
    in main: 0x16cf96dbb
    0x16cf96dbb destructor
  */
  return 0;
}

此时发生一次构造,一次析构。也就是main中的t取代了临时空间,而b的构造完成了t的构造。所在完成了一次构造,一次析构。
此时t的地址,同b的地址

A funcc()
{
  A b;
  cout << "in funcc &b " << &b << endl;
  return b;
}
int main()
{
  A t;
  cout << "in main &t" << &t << endl;
  t = funcc();
  /*
    0x16d25adbb constructor
    in main &t0x16d25adbb
    0x16d25adab constructor
    in funcc &b 0x16d25adab
    0x16d25adbb operator = 0x16d25adab
  */
  return 0;
}

此时发生了两次构造,一次赋值,两次析构,其中b的构造,完成了main函数中临时空间的构造,构造的临时对象作为赋值运算符重载的参数传入。然后发生赋值。两次析构分别是临时空间和t的析构

9.3. c++返回栈对象引用

返回栈对象的引用,多用于产生串联应用。比如连等式。栈对象是不可以返回引用的。除非,函数的调用者返回自身对象

// 编译器提供默认(编译成功的原因)。一旦自定义,系统不再提供默认
// 默认赋值运算符重载也是一种等位赋值。浅拷贝,浅赋值
// 浅赋值,有可能会导致自身内存泄漏、内存发生重析构、自赋值
myString &myString::operator=(const myString &another)
{
  if (this == &another)
    return *this;
  delete[] this->_str;
  int len = strlen(another._str);
  this->_str = new char[len + 1];
  strcpy(this->_str, another._str);
  return *this;
}

提高:非要返回栈引用会发生什么

class AA
{
public:
  AA()
  {
    cout << this << " constructor" << endl;
  }
  AA(const AA &a)
  {
    cout << this << " cp constructor from " << &a << endl;
  }
  ~AA()
  {
    cout << this << " destructor" << endl;
  }
};
const AA &func1()
{
  AA b;
  cout << "in func1 &a" << &b << endl;
  return b;
}
int main()
{
  AA t = func1();
  cout << "int main &t " << &t << endl;
  /*
    0x16d7d6d7f constructor
    in func1 &a0x16d7d6d7f
    0x16d7d6d7f destructor
    0x16d7d6dbb cp constructor from 0x16d7d6d7f
    int main &t 0x16d7d6dbb
    0x16d7d6dbb destructor
  */
  return 0;
}

返回的引用,完成了一次拷贝,但是被拷贝的对象,已经析构。结果是未知的,所以不要返回栈上的引用。

10. 案例:string 与 MyString

10.1. string的使用

int main()
{
	// string s("china");
	string s = "china";
	string s2(s);
	string s3 = s2;
	string s4;
	s4 = s3;
}

10.2. MyString的声明

#ifndef MYSTRING_H_
#define MYSTRING_H_
#include <stddef.h>
#include <iostream>
class MyString {
public:
	MyString(const char *str=NULL);
	MyString(const MyString & other);
	MyString & operator=(const MyString & another);
	MyString operator+(const MyString & other);
	bool operator==(const MyString &other);
	bool operator>(const MyString &other);
	bool operator<(const MyString &other);
	char& operator[](int idx);
	void dis();
	virtual ~MyString();
private:
	char * _str;
};
#endif /* MYSTRING_H_ */

10.3. 构造

MyString::MyString(const char *str) {
	if(str == NULL)
	{
		_str = new char[1];
		*_str = '\0';
	}
	else
	{
		int len = strlen(str);
		_str = new char[len+1];
		strcpy(_str,str);
	}
}

10.4. 析构

MyString::~MyString() {
	delete []_str;
}

10.5. 拷贝构造(深拷贝)

MyString::MyString(const MyString & other)
{
	int len = strlen(other._str);
	this->_str = new char[len+1];
	strcpy(this->_str,other._str);
}

10.6. 赋值运算符重载

MyString & MyString::operator=(const MyString & another)
{
	if(this == &another)
		return *this;
		else 
		{
			delete []this->_str;
			int len = strlen(another._str);
			this->_str = new char[len+1];
			strcpy(this->_str,another._str);
			return *this;
		}
}

10.7. 加法运算符重载

MyString MyString::operator+(const MyString & other)
{
	int len = strlen(this->_str) + strlen(other._str);
	MyString str;
	delete []str._str;
	str._str = new char[len+1];
	memset(str._str,0,len+1);
	strcat(str._str,this->_str);
	strcat(str._str,other._str);
	return str;
}

10.8. 关系运算符重载

bool MyString::operator==(const MyString &other)
{
	if(strcmp(this->_str,other._str) == 0)
		return true;
	else
		return false;
	}
bool MyString::operator>(const MyString &other)
{
	if(strcmp(this->_str,other._str) > 0)
		return true;
	else
		return false;
}
bool MyString::operator<(const MyString &other)
{
	if(strcmp(this->_str,other._str) < 0)
		return true;
	else
		return false;
}

10.9. []运算符重载

char& MyString::operator[](int idx)
{
	return _str[idx];
}

10.10. 测试

#include <iostream>
#include "mystring.h"
using namespace std;
int main()
{
	// MyString s = "china";
	MyString s("china"); //构造器
	s.dis();
	// MyString s2 = s;
	MyString s2(s); //拷贝构造器
	s2.dis();
	MyString s3;
	s3 = s2 = s; //赋值运算符重载
	s3.dis();
	MyString s4;
	s4 = "america"; //"america" 无名对象的构造器,赋值运算符重载
	return 0;
}

11. 练习

11.1. 实现钟表类

属性:时,分,秒
行为:run() 在屏幕上实现电子时钟 13:34:45 每隔一秒更新一个显示

11.2. 分析

构造时,初始化为当前系统时间,然后每隔一秒,刷屏

11.3. 代码

// 初始化的数据来自系统
class Clock
{
public:
  Clock()
  {
    time_t t = time(NULL);
    struct tm ti = *localtime(&t);
    hour = ti.tm_hour;
    minute = ti.tm_min;
    second = ti.tm_sec;
  }
  void run()
  {
    while (1)
    {
      show(); // 完成显示
      tick(); // 完成数据更新
    }
  }

private:
  void show()
  {
    system("cls");
    cout << setw(2) << setfill('0') << hour << ":";
    cout << setw(2) << setfill('0') << minute << ":";
    cout << setw(2) << setfill('0') << second;
  };
  void tick()
  {
    sleep(1);
    if (++second == 60)
    {
      second = 0;
      if (++minute == 60)
      {
        minute = 0;
        if (++hour == 24)
        {
          hour = 0;
        }
      }
    }
  };
  int hour;
  int minute;
  int second;
};
int main()
{
  Clock c;
  c.run();
  return 0;
}

12. 栈和堆上的对象及对象数组

12.1. 引例

class Stu
{
public:
    Stu(string n) : _name(n) {}
    void dis()
    {
        cout << _name << endl;
    }

private:
    string _name;
};
int main()
{
    // Stu s; // 没有无参构造器
    // Stu s[5] = {Stu("zhangsan"), Stu("lisi")}; // 不能指定个数,或部分初始化,会报错
    Stu s[] = {Stu("zhangsan"), Stu("lisi")};

    // Stu *ps = new Stu[4]{Stu("zhangsan")}; // 必须对指定的类个数初始化,否则会报错
    Stu *ps = new Stu[1]{Stu("zhangsan")};
    return 0;
}

12.2. 用new和delete生成销毁堆对象

new一个堆对象,会自动调用构造函数,delete一个堆对象会自动调用析构函数。

12.3. 栈对象数组

如果生成的数组未初始化,则调用无参构造器,或手动调用带参构造器

12.4. 堆对象数组

如果生成的数组未初始化,则调用无参构造器,或手动调用带参构造器

12.5. 讨论

构造器无论重载还是默认参数,一定要把系统默认的无参构造器包含起来,不然生成数组的时候,可能会有些麻烦

13. 成员函数的存储方式

13.1. 类成员可能的组成

用类去定义对象时,系统会为每一个对象分配存储空间。如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间
按理说,如果用同一个类定义了10个对象,那么就需要分别为10个对象的数据和函数代码分配存储单元
在这里插入图片描述

13.2. 类成员实际的组成

能否只用一段空间来存放这个共同的函数代码段,在调用各对象的函数时,都去调用这个公共的函数代码
在这里插入图片描述
显然,这样做会大大节约存储空间。C++编译系统正是这样做的,因此每个对象所占用的存储空间只是该对象的数据部分所占用的存储空间,而不包括函数代码所占用的存储空间

class Time 
{
public:
	void dis() 
	{
		cout << hour << minute << sec << endl;
	}
private:
	int hour;
	int minute;
	int sec;
}
int main() 
{
	cout << sizeof(Time) << endl; // 12
	// 一个对象所占的空间大小只取决于该对象中数据成员所占的空间,而与成员函数无关
	return 0;
}

13.3. 调用原理

所有对象都调用公共的函数代码段,执行不同的代码可能有不同的结果,如果区分的?因为C++设置了this指针,this指针指向调用该函数的不同对象。当t调用dis()函数时,this指向t。当t1调用dis()函数时,this指向t1

class Time 
{
public: 
	Time(int h, int m, int s) 
		:hour(h), minute(m), sec(s) {}
	void dis() // void dis(Time *p)
	{
		cout << "this = " << this << endl;
		cout << this->hour << ":" << this->minute << ":" << this->sec << endl;
	}
private:
	int hour;
	int minute;
	int sec;
}
int main() 
{
	Time t(1,2,3);
	Time t2(2,3,4);
	t.dis(); // 等价于t.dis(&t)
	t2.dis();
	return 0;
}

13.4. 注意事项

  1. 不论成员函数在类内定义还是在类外定义,成员函数的代码都用同一种方式存储
  2. 不要将成员函数的这种存储方式和inline(内置)函数的概念混淆。inline的逻辑意义是将函数内嵌到调用代码处,减少压栈与出栈的开支
  3. 应当说明,常说的某某对象的成员函数,是从逻辑角度而言的,而成员函数的存储方式,是从物理角度而言的,二者是不矛盾的。类似于二维数组是逻辑概念,而物理存储是线性概念。

14. const修饰符

14.1. 常数据成员

const修饰类的成员变量,表示成员常量,不能被修改,同时他只能在初始化列表中赋值。可被const和非const成员函数调用,而不可以修改

class A
{
public:
	A():iValue(100) {}
private:
	const int iValue;
}

14.2. 常成员函数

14.2.1. const修饰函数的意义

承诺在本函数内部不会修改类内的数据成员,不会调用其他非const成员函数

14.2.2. const修饰函数位置

const修饰函数放在声明之后,实现体之前,要求在声明和定义出都要有const修饰

void dis() const
{}

14.2.3. const构成函数重载

class B
{
public:
    B() : x(100), y(200) {}
    void dis() const // const对象调用时,优先调用
    {
        // input(); 不能调用非const函数,因为本函数不会修改,无法保证所调的函数也不会修改
        cout << "x " << x << endl;
        cout << "y " << y << endl;
        // y = 200; const修饰函数表示承诺不对数据成员修改
    }
    // 此时构成重载,非const对象时,优先调用
    void dis()
    {
        y = 200;
        input();
        cout << "x " << x << endl;
        cout << "y " << y << endl;
    }
    void input()
    {
        cin >> y;
    }

private:
    const int x;
    int y;
};
int main()
{
    B b;
    b.dis(); // void dis => x 100 y 200

    // const B b;
    // b.dis(); // void dis const => x 100 y 200

    return 0;
}

小结:

  1. 如果const构成函数重载,const对象只能调用const函数,非const对象优先调用非const函数
  2. const函数只能调用const函数。非const函数可以调用const函数
  3. 类体外定义的const成员函数,在定义和声明处都需要const修饰符

14.3. 常对象

const A a;
a.dis();

小结:

  1. const对象,只能调用const成员函数
  2. 可访问const或非const数据成员,不能修改

15. static修饰符

在c++中,静态成员是属于整个类的,而不是某个对象,静态成员变量只存储一份供对象共用,因此在所有对象中都可以共享。使用静态成员变量实现多个对象之间的数据共享**不会破坏隐藏(相比全局变量的优点)**的原则,保证了安全性且节省内存

15.1. 类静态数据成员的定义及初始化

15.1.1. 声明

static 数据类型 成员变量; // 在类的内部

15.1.2. 初始化

数据类型 类名::静态数据成员 = 初值; // 在类的外部
// 在生成对象时,普通数据成员才有空间,而static成员在类声明的时候,就已经开辟了空间,就在data rw段
// static数据成员,既属于类,也属于对象,但终归属于类
// 初始化:类内定义,类外初始化
class AA
{
public:
    void func()
    {
        cout << share << endl;
    };
    int x;
    int y;
    static int share;
};
int AA::share = 0;
int main()
{
    AA a, b, c;
    a.func();                                      // 0
    cout << "sizeof(AA) = " << sizeof(AA) << endl; // 8
    cout << "sizeof(a) = " << sizeof(a) << endl;   // 8
    cout << "sizeof(b) = " << sizeof(b) << endl;   // 8
    cout << "sizeof(c) = " << sizeof(c) << endl;   // 8
    return 0;
}

15.1.3. 调用

类名::静态数据成员
类对象.静态数据成员
// 类本质上也是一个命名空间
class AA
{
public:
    void func()
    {
        cout << share << endl;
    };
    void modify(int m)
    {
        share = m;
    };
    int x;
    int y;
    static int share;
};
int AA::share = 0;
int main()
{
    // AA a, b, c;
    // a.func(); // 0
    // a.modify(200);
    // b.func(); // 200
    // c.func(); // 200

    cout << AA::share << endl; // 0
    AA a;
    a.func(); // 0
    return 0;
}

15.1.4. 案例

中国校园设计的“一塔湖图”

// static修饰成员函数,作用:用于管理static成员
// static修饰的成员函数,既属于类也属于对象,但终归属于类
// static修饰的成员函数,因为属于类,所以没有this指针,不能访问非static数据成员及成员函数
class School
{
public:
    string &getTower()
    {
        return tower;
    }
    static string &getLib()
    {
        // tower = "tower"; // Error
        // getTower(); // Error
        return lib;
    }

private:
    string tower;
    string lake;
    static string lib;
};
// 类外实例化
string School::lib = "";
int main()
{
    School::getLib() = "book1";
    School::getLib() += " book2";
    School hut, buct;
    hut.getTower() = "tower1";
    buct.getTower() = "tower2";
    hut.getLib() += " book3";
    buct.getLib() += " book4";

    cout << hut.getLib() << endl;
    return 0;
}

15.1.5. 小结

  1. static成员变量实现了同族类对象间信息共享
  2. static成员类外存储,求类大小,并不包含在内
  3. static成员是命名空间属于类的全局变量,存储在data区的rw段
  4. static成员使用时必须初始化,且只能类外初始化
  5. 可以访问类名(无对象生成时亦可),也可通过对象访问

15.2. 类静态成员函数的定义

为了管理静态成员,c++提供了静态函数,以及对外提供接口。并且静态函数只能访问静态成员

15.2.1. 声明

static 函数声明

15.2.2. 调用

类名::函数调用
类对象.函数调用

15.2.3. 案例

class Student
{
public:
    Student(int n, int a, float s) : num(n), age(a), score(s){};
    void total()
    {
        count++;
        sum += score;
    }
    static float average();

private:
    int num;
    int age;
    float score;
    static float sum;
    static int count;
};
float Student::sum = 0;
int Student::count = 0;
float Student::average()
{
    return sum / count;
};
int main()
{
    Student stu[3] = {
        Student(1001, 14, 70),
        Student(1002, 15, 34),
        Student(1003, 16, 90)};
    for (int i = 0; i < 3; i++)
    {
        stu[i].total();
    }
    cout << Student::average() << endl; // 64.6667
    return 0;
}

15.2.4. 小结

  1. 静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装
  2. 静态成员函数只能访问静态数据成员。原因:非静态成员函数,在调用this指针时被当作参数传进。而静态成员函数属于类,不属于对象,没有this指针

15.3. 综合案例-cocos渲染树的模拟

每生成一个节点,自动挂到静态表头上去


class Student
{
public:
    Student(string n) : name(n)
    {
        if (head == NULL)
        {
            head = this;
            this->next = NULL;
        }
        else
        {
            this->next = head;
            head = this;
        }
    }
    static void printStudentList();
    static void deleteStudentList();

private:
    string name;
    Student *next;
    static Student *head;
};
void Student::printStudentList()
{
    Student *p = head;
    while (p != NULL)
    {
        cout << p->name << endl;
        p = p->next;
    }
};
void Student::deleteStudentList()
{
    Student *p = head;
    while (head)
    {
        head = head->next;
        delete p;
        p = head;
    }
}
Student *Student::head = NULL;

int main()
{
    std::string prefix = "stu";
    std::string name;
    for (int i = 0; i < 10; i++)
    {
        name = prefix + std::to_string(i);
        new Student(name);
    }
    // stu9 stu8 stu7 stu6 stu5 stu4 stu3 stu2 stu1 stu0
    Student::printStudentList();
    Student::deleteStudentList();
    return 0;
}

16. static const成员

如果一个类的成员,既要实现共享,又要实现不可改变,那就用static const修饰。修饰成员函数,格式并无二异,修饰数据成员。必须要类内部实例化

class AAA
{
public:
    static const void dis()
    {
        cout << a << endl;
    }

private:
    const static int a = 100;
};
int main()
{
    AAA::dis(); // 100
    return 0;
}

17. 指向类成员的指针

在c++中,可以定义一个指针,使其指向类成员或成员函数,然后通过指针来访问类的成员。这包括指向属性成员的指针和指向成员函数的指针

17.1. 指向普通变量和函数的指针

void func(int a)
{
    cout << a << endl;
}
int main()
{
    int a = 10;
    int *p = &a;
    cout << *p << endl; // 10
    void (*pf)(int) = func;
    pf(100); // 100
    return 0;
}

17.2. 指向类数据成员的指针

  • 定义
<数据类型><类名>::*<指针名>
  • 赋值&初始化
<数据类型><类名>::*<指针名>[=&<类名>::<非静态数据成员>]

指向非静态数据成员的指针在定义时必须和类相关联,在使用时必须和具体的对象关联。

  • 解引用
    由于类不是运行时存在的对象。因此在使用这类指针时,需要首先指定类的一个对象,然后通过对象来引用指针所指向的成员
<类对象名>.*<指向非静态数据成员的指针>
<类对象指针>->*<指向非静态数据成员的指针>
  • 案例
class Student
{
public:
    Student(string n, int nu) : name(n), num(nu) {}
    string name;
    int num;
};
int main()
{
    Student s("zhangsan", 1001);
    Student s1("lisi", 1002);

    string Student::*ps = &Student::name;
    cout << s.*ps << endl;  // zhangsan
    cout << s1.*ps << endl; // lisi

    Student *pp = new Student("wangwu", 1003);
    cout << pp->*ps << endl; // wangwu
    return 0;
}

17.3. 指向类成员函数的指针

定义一个指向非静态成员函数的指针,参数列表要相同、返回类型要相同、所属类型要相同,必须要这三个方面与其指向的成员函数保持一致

  • 定义
<数据类型>(<类名>::*<指针名>)(<参数列表>)
  • 赋值&初始化
<数据类型>(<类名>::*<指针名>)(<参数列表>)([=&<类名>::<非静态成员函数>])
  • 解引用
    由于类不是运行时存在的对象,因此在使用这类指针时,需首先指定类的一个对象,然后通过对象来引用指针指向的成员
(<类对象名>).*<指向非静态成员函数的指针>)(<参数列表>)
(<类对象指针>)-><指向非静态成员函数的指针>)(<参数列表>)
  • 案例
class Student
{
public:
    Student(string n, int nu) : name(n), num(nu) {}
    void dis()
    {
        cout << "name: " << name << ", num: " << num << endl;
    }

private:
    string name;
    int num;
};
int main()
{
    Student s("zhangsan", 1001);
    Student s1("lisi", 1002);
    Student *ps = new Student("lisi", 1003);

    void (Student::*pf)() = &Student::dis;
    (s.*pf)();   // name: zhangsan, num: 1001
    (s1.*pf)();  // name: lisi, num: 1002
    (ps->*pf)(); // name: lisi, num: 1003
    return 0;
}

17.4. 指向类成员指针小结

与普通意义上的指针不一样。存放的是偏移量。指向非静态成员函数时,必须用类名作限定符,使用时则必须用类的实例作限定符。指向静态成员函数时,则不需要使用类名做限定符。

17.5. 应用提高

用指向类函数的指针,实现更加隐蔽的接口

class Widget
{
public:
  Widget()
  {
    fptr[0] = &f;
    fptr[1] = &g;
    fptr[2] = &h;
    fptr[3] = &i;
  }
  void select(int idx, int val)
  {
    if (idx < 0 || idx > cnt)
      return;
    (this->*fptr[idx])(val);
  }
  int count()
  {
    return cnt;
  }

private:
  void f(int val) { cout << "void f() " << val << endl; }
  void g(int val) { cout << "void g() " << val << endl; }
  void h(int val) { cout << "void h() " << val << endl; }
  void i(int val) { cout << "void i() " << val << endl; }
  enum
  {
    cnt = 4
  };
  void (Widget::*fptr[cnt])(int);
};
int main()
{
  Widget w;
  for (int i = 0; i < w.count(); i++)
  {
    w.select(i, 1);
  }
  return 0;
}

17.6. 指向类静态成员的指针

  • 指向类静态数据成员的指针
    指向静态数据成员的指针的定义和使用与普通指针相同,在定义时无须和类相关联,在使用时也无须和具体的对象相关联
  • 指向类静态成员函数的指针
    指向静态成员函数的指针和普通指针相同,在定义时无须和类相关联,在使用时也无须和具体的对象相关联
class AAAA
{
public:
  static void dis();
  static int data;
};
void AAAA::dis()
{
  cout << data << endl;
}
int AAAA::data = 100;
int main()
{
  int *p = &AAAA::data;
  cout << *p << endl;
  void (*pfunc)() = &AAAA::dis;
  pfunc();
  return 0;
}
  • 34
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qi妙代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值