×

成员变量[详解]

前端技术网 前端技术网 发表于2023-12-20 00:45:34 浏览738 评论0

抢沙发发表评论

一、C++类,详解

推荐先看书系统学习下吧《C++程序设计》

一、C++类的定义

成员变量[详解]

C++中使用关键字 class来定义类,其基本形式如下:

class类名

{

public:

//行为或属性

protected:

成员变量[详解]

//行为或属性

private:

//行为或属性

};

示例:

定义一个点(Point)类,具有以下属性和方法:

■属性: x坐标, y坐标

■方法: 1.设置x,y的坐标值; 2.输出坐标的信息。

实现代码:

class Point

{

public:

void setPoint(int x, int y);

void printPoint();

private:

int xPos;

int yPos;

};

代码说明:

上段代码中定义了一个名为 Point的类,具有两个私密属性, int型的xPos和yPos,分别用来表示x点和y点。

在方法上, setPoint用来设置属性,也就是 xPos和 yPos的值; printPoint用来输出点的信息。

1数据抽象和封装

抽象是通过特定的实例抽取共同特征以后形成概念的过程。一个对象是现实世界中一个实体的抽象,一个类是一组对象的抽象。

封装是将相关的概念组成一个单元,然后通过一个名称来引用它。面向对象封装是将数据和基于数据的操作封装成一个整体对象,对数据的访问或修改只能通过对象对外提供的接口进行。

2类定义

几个重要名词:

(1)类名

遵循一般的命名规则;字母,数字和下划线组合,不要以数字开头。

(2)类成员

类可以没有成员,也可以定义多个成员。成员可以是数据、函数或类型别名。所有的成员都必须在类的内部声明。

没有成员的类是空类,空类也占用空间。

class People

{

};

sizeof(People)= 1;

(3)构造函数

构造函数是一个特殊的、与类同名的成员函数,用于给每个数据成员设置适当的初始值。

(4)成员函数

成员函数必须在类内部声明,可以在类内部定义,也可以在类外部定义。如果在类内部定义,就默认是内联函数。

3类定义补充

3.1可使用类型别名来简化类

除了定义数据和函数成员之外,类还可以定义自己的局部类型名字。

使用类型别名有很多好处,它让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。

class People

{

public:

typedef std::string phonenum;//电话号码类型

phonenum phonePub;//公开号码

private:

phonenum phonePri;//私人号码

};

3.2成员函数可被重载

可以有多个重载成员函数,个数不限。

3.3内联函数

有三种:

(1)直接在类内部定义。

(2)在类内部声明,加上inline关键字,在类外部定义。

(3)在类内部声明,在类外部定义,同时加上inline关键字。注意:此种情况下,内联函数的定义通常应该放在类定义的同一头文件中,而不是在源文件中。这是为了保证内联函数的定义在调用该函数的每个源文件中是可见的。

3.4访问限制

public,private,protected为属性/方法限制的关键字。

3.5类的数据成员中不能使用 auto、extern和register等进行修饰,也不能在定义时进行初始化

如 int xPos= 0;//错;

例外:

静态常量整型(包括char,bool)数据成员可以直接在类的定义体中进行初始化,例如:

static const int ia= 30;

4类声明与类定义

4.1类声明(declare)

class Screen;

在声明之后,定义之前,只知道Screen是一个类名,但不知道包含哪些成员。只能以有限方式使用它,不能定义该类型的对象,只能用于定义指向该类型的指针或引用,声明(不是定义)使用该类型作为形参类型或返回类型的函数。

void Test1(Screen& a){};

void Test1(Screen* a){};

4.2类定义(define)

在创建类的对象之前,必须完整的定义该类,而不只是声明类。所以,类不能具有自身类型的数据成员,但可以包含指向本类的指针或引用。

class LinkScreen

{

public:

Screen window;

LinkScreen* next;

LinkScreen* prev;

};//注意,分号不能丢

因为在类定义之后可以接一个对象定义列表,可类比内置类型,定义必须以分号结束:

class LinkScreen{/*...*/};

class LinkScreen{/*...*/} scr1,scr2;

5类对象

定义类对象时,将为其分配存储空间。

Sales_item item;//编译器分配了足以容纳一个 Sales_item对象的存储空间。item指的就是那个存储空间。

6隐含的 this指针

成员函数具有一个附加的隐含形参,即 this指针,它由编译器隐含地定义。成员函数的函数体可以显式使用 this指针。

6.1何时使用 this指针

当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。最常见的情况是在这样的函数中使用 this:该函数返回对调用该函数的对象的引用。

class Screen

{

...

public:

Screen& set(char);

};

Screen& Screen::set(char c)

{

contents[cursor]= c;

return*this;

}

7类作用域

每个类都定义了自己的作用域和唯一的类型。

类的作用域包括:类的内部(花括号之内),定义在类外部的成员函数的参数表(小括号之内)和函数体(花括号之内)。

class Screen

{

//类的内部

...

};

//类的外部

char Screen::get(index r, index c) const

{

index row= r* width;// compute the row location

return contents[row+ c];// offset by c to fetch specified character

}

注意:成员函数的返回类型不一定在类作用域中。可通过类名::来判断是否是类的作用域,::之前不属于类的作用域,::之后属于类的作用域。例如

Screen::之前的返回类型就不在类的作用域,Screen::之后的函数名开始到函数体都是类的作用域。

class Screen

{

public:

typedef std::string::size_type index;

index get_cursor() const;

};

Screen::index Screen::get_cursor() const//注意:index前面的Screen不能少

{

return cursor;

}

该函数的返回类型是 index,这是在 Screen类内部定义的一个类型名。在类作用域之外使用,必须用完全限定的类型名 Screen::index来指定所需要的 index是在类 Screen中定义的名字。

二构造函数

构造函数是特殊的成员函数,用来保证每个对象的数据成员具有合适的初始值。

构造函数名字与类名相同,不能指定返回类型(也不能定义返回类型为void),可以有0-n个形参。

在创建类的对象时,编译器就运行一个构造函数。

1构造函数可以重载

可以为一个类声明的构造函数的数量没有限制,只要每个构造函数的形参表是唯一的。

class Sales_item;

{

public:

Sales_item(const std::string&);

Sales_item(std::istream&);

Sales_item();//默认构造函数

};

2构造函数自动执行

只要创建该类型的一个对象,编译器就运行一个构造函数:

Sales_item item1("0-201-54848-8");

Sales_item*p= new Sales_item();

第一种情况下,运行接受一个 string实参的构造函数,来初始化变量item1。

第二种情况下,动态分配一个新的 Sales_item对象,通过运行默认构造函数初始化该对象。

3构造函数初始化式

与其他函数一样,构造函数具有名字、形参表和函数体。

与其他函数不同的是,构造函数可以包含一个构造函数初始化列表:

Sales_item::Sales_item(const string&book): i**n(book), units_sold(0), revenue(0.0)

{}

构造函数初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。

构造函数可以定义在类的内部或外部。构造函数初始化只在构造函数的定义中指定。

构造函数分两个阶段执行:(1)初始化阶段;(2)普通的计算阶段。初始化列表属于初始化阶段(1),构造函数函数体中的所有语句属于计算阶段(2)。

初始化列表比构造函数体先执行。不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。

3.1哪种类需要初始化式

const对象或引用类型的对象,可以初始化,但不能对它们赋值,而且在开始执行构造函数的函数体之前要完成初始化。

初始化 const或引用类型数据成员的唯一机会是构造函数初始化列表中,在构造函数函数体中对它们赋值不起作用。

没有默认构造函数的类类型的成员,以及 const或引用类型的成员,必须在初始化列表中完成初始化。

class ConstRef

{

public:

ConstRef(int ii);

private:

int i;

const int ci;

int&ri;

};

ConstRef::ConstRef(int ii)

{

i= ii;// ok

ci= ii;// error

ri= i;//

}

应该这么初始化:

ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii){}

3.2成员初始化的次序

每个成员在构造函数初始化列表中只能指定一次。重复初始化,编译器一般会有提示。

成员被初始化的次序就是定义成员的次序,跟初始化列表中的顺序无关。

3.3初始化式表达式

初始化式可以是任意表达式

Sales_item(const std::string&book, int cnt, double price): i**n(book), units_sold(cnt), revenue(cnt* price){}

3.4类类型的数据成员的初始化式

初始化类类型的成员时,要指定实参并传递给成员类型的一个构造函数,可以使用该类型的任意构造函数。

Sales_item(): i**n(10,'9'), units_sold(0), revenue(0.0){}

3.5类对象的数据成员的初始化

在类A的构造函数初始化列表中没有显式提及的每个成员,使用与初始化变量相同的规则来进行初始化。

类类型的数据成员,运行该类型的默认构造函数来初始化。

内置或复合类型的成员的初始值依赖于该类对象的作用域:在局部作用域中不被初始化,在全局作用域中被初始化为0。假设有一个类A,

class A

{

public:

int ia;

B b;

};

A类对象A a;不管a在局部作用域还是全局作用域,b使用B类的默认构造函数来初始化,ia的初始化取决于a的作用域,a在局部作用域,ia不被初始化,a在全局作用域,ia初始化0。

4默认构造函数

不含形参的构造函数就是默认构造函数。

只要定义一个对象时没有提供初始化式,就使用默认构造函数。如: A a;

为所有形参提供默认实参的构造函数也定义了默认构造函数。例如:

class A

{

public:

A(int a=1,char c=''){}

private:

int ia;

char c1;

};

4.1合成的默认构造函数

只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。

一个类只要定义了一个构造函数,编译器也不会再生成默认构造函数。

建议:

如果定义了其他构造函数,也提供一个默认构造函数。

如果类包含内置或复合类型(如 int&或 string*)的成员,它应该定义自己的构造函数来初始化这些成员。每个构造函数应该为每个内置或复合类型的成员提供初始化。

5隐式类类型转换

5.1只含单个形参的构造函数能够实现从形参类型到该类类型的一个隐式转换

class A

{

public:

A(int a)

{

ia=a;

}

bool EqualTo(const A& a)

{

return ia== a.ia;

}

private:

int ia;

};

A a(1);

bool bEq= false;

bEq= a.EqualTo(1);//参数为1,实现从int型到A的隐式转换

5.2抑制由构造函数定义的隐式转换

通过将构造函数声明为 explicit,来防止在需要隐式转换的上下文中使用构造函数:

class A

{

public:

explicit A(int a)

{

ia=a;

}

bool EqualTo(const A& a)

{

return ia== a.ia;

}

private:

int ia;

};

通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为 explicit。将构造函数设置为 explicit可以避免错误。

三**控制

1**构造函数

1.1几个要点

(1)**构造函数

**构造函数是一种特殊构造函数,只有1个形参,该形参(常用 const&修饰)是对该类类型的引用。

class Peopel

{

public:

Peopel();//默认构造函数

Peopel(const Peopel&);//**构造函数

~Peopel();//析构函数

};

当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用**构造函数。

Peopel a1; Peopel a2= a1;

当将该类型的对象传递给函数或函数返回该类型的对象时,将隐式使用**构造函数。

Peopel Func(Peopel b){...}

(2)析构函数

析构函数是构造函数的互补:当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。

析构函数可用于释放构造对象时或在对象的生命期中所获取的资源。

不管类是否定义了自己的析构函数,编译器都自动执行类中非 static数据成员的析构函数。

(3)**控制

**构造函数、赋值操作符和析构函数总称为**控制。编译器自动实现这些操作,但类也可以定义自己的版本。

(4)两种初始化形式

C++支持两种初始化形式:直接初始化和**初始化。直接初始化将初始化式放在圆括号中,**初始化使用=符号。

对于内置类型,例如int, double等,直接初始化和**初始化没有区别。

对于类类型:直接初始化直接调用与实参匹配的构造函数;**初始化先使用指定构造函数创建一个临时对象,然后用**构造函数将那个临时对象**到正在创建的对象。直接初始化比**初始化更快。

(5)形参和返回值

当形参或返回值为类类型时,由该类的**构造函数进行**。

(6)初始化容器元素

**构造函数可用于初始化顺序容器中的元素。例如:

vector<string> svec(5);

编译器首先使用 string默认构造函数创建一个临时值,然后使用**构造函数将临时值**到 svec的每个元素。

(7)构造函数与数组元素

如果没有为类类型数组提供元素初始化式,则将用默认构造函数初始化每个元素。

如果使用常规的花括号括住的数组初始化列表来提供显式元素初始化式,则使用**初始化来初始化每个元素。根据指定值创建适当类型的元素,然后用**构造函数将该值**到相应元素:

Sales_item primer_eds[]={ string("0-201-16487-6"),

string("0-201-54848-8"),

string("0-201-82470-1"),

Sales_item()

};

1.2合成的**构造函数

(1)合成的**构造函数

如果没有定义**构造函数,编译器就会为我们合成一个。

合成**构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本。

逐个成员初始化:合成**构造函数直接**内置类型成员的值,类类型成员使用该类的**构造函数进行**。

例外:如果一个类具有数组成员,则合成**构造函数将**数组。**数组时合成**构造函数将**数组的每一个元素。

1.3定义自己的**构造函数

(1)只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义**构造函数,也可以**。

class Peopel

{

public:

std::string name;

unsigned int id;

unsigned int age;

std::string address;

};

(2)有些类必须对**对象时发生的事情加以控制。

例如,类有一个数据成员是指针,或者有成员表示在构造函数中分配的其他资源。而另一些类在创建新对象时必须做一些特定工作。这两种情况下,都必须定义自己的**构造函数。

最好显式或隐式定义默认构造函数和**构造函数。如果定义了**构造函数,必须定义默认构造函数。

1.4禁止**

有些类需要完全禁止**。例如,iostream类就不允许**。延伸:容器内元素不能为iostream

为了防止**,类必须显式声明其**构造函数为 private。

2赋值操作符

与**构造函数一样,如果类没有定义自己的赋值操作符,则编译器会合成一个。

(1)重载赋值操作符

Sales_item& operator=(const Sales_item&);

(2)合成赋值操作符

合成赋值操作符会逐个成员赋值:右操作数对象的每个成员赋值给左操作数对象的对应成员。除数组之外,每个成员用所属类型的常规方式进行赋值。对于数组,给每个数组元素赋值。

(3)**和赋值常一起使用

一般而言,如果类需要**构造函数,它也会需要赋值操作符。

3析构函数

构造函数的用途之一是自动获取资源;与之相对的是,析构函数的用途之一是回收资源。除此之外,析构函数可以执行任意类设计者希望在该类对象的使用完毕之后执行的操作。

(1)何时调用析构函数

撤销(销毁)类对象时会自动调用析构函数。

变量(类对象)在超出作用域时应该自动撤销(销毁)。

动态分配的对象(new A)只有在指向该对象的指针被删除时才撤销(销毁)。

撤销(销毁)一个容器(不管是标准库容器还是内置数组)时,也会运行容器中的类类型元素的析构函数(容器中的元素总是从后往前撤销)。

(2)何时编写显式析构函数

如果类需要定义析构函数,则它也需要定义赋值操作符和**构造函数,这个规则常称为三法则:如果类需要析构函数,则需要所有这三个**控制成员。

(3)合成析构函数

合成析构函数按对象创建时的逆序撤销每个非 static成员,因此,它按成员在类中声明次序的逆序撤销成员。

对于每个类类型的成员,合成析构函数调用该成员的析构函数来撤销对象。

合成析构函数并不删除指针成员所指向的对象。所以,如果有指针成员,一定要定义自己的析构函数来删除指针。

析构函数与**构造函数或赋值操作符之间的一个重要区别:即使我们编写了自己的析构函数,合成析构函数仍然运行。

四友元

友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类。

友元可以出现在类定义的内部的任何地方。

友元不是授予友元关系的那个类的成员,所以它们不受声明出现部分的访问控制影响。

建议:将友元声明成组地放在类定义的开始或结尾。

1友元类

class Hu**and

{

public:

friend class Wife;

private:

double money;//钱是老公私有的,别人不能动,但老婆除外

};

class Wife

{

public:

void Consume(Hu**and& h)

{

h.money-= 10000;//老婆可以花老公的钱

}

};

Hu**and h;

Wife w;

w.Consume(h);

2使其他类的成员函数成为友元

class Hu**and;//1.声明Hu**and

class Wife//2.定义Wife类

{

public:

void Consume(Hu**and& h);

};

class Hu**and//3.定义Hu**and类

{

public:

friend void Wife::Consume(Hu**and& h);//声明Consume函数。

private:

double money;//钱是老公私有的,别人不能动,但老婆除外

};

void Wife::Consume(Hu**and& h)//4.定义Consume函数。

{

h.money-= 10000;//老婆可以花老公的钱

}

注意类和函数的声明和定义的顺序:

(1)声明类Hu**and

(2)定义类Wife,声明Consume函数

(3)定义类Hu**and

(4)定义Consume函数。

二、C语言选择题,详解

C

struct student为结构体类型

引用结构体变量中的成员的值的方式为:

结构体变量名.成员名

【A不符合】

通过指针引用结构体变量是用(*p).age表示。

(*p)表示p所指向的结构体变量,(*p).age是p指向的结构体变量中的成员age。

注意*p两侧的括号不可省,因为成员运算符“.”优先于“*”运算符,*p.age就等价于*(p.age)了。

为了使用方便和直观,C语言允许把(*p),age用p->age来代替,“->”代表一个箭头,p->age表示P所指向的结构体变量中的age成员。同样,(*p).age等价于p->age。“->”称为指向运算符。

如果p指向一个结构体变量stu,以下三种用法等价:

①stu.成员名(如stu.age);

②(*p).成员名(如(*p).age);

③p->成员名(如p->age)。

【B、D不符合】

三、JAVA 类变量和成员变量怎么理解

成员变量就是类中的属性。当new对象的时候,每个对象都有一份属性。一个对象中的属性就是成员变量。在类内部,任何地方都可以访问成员变量。类变量是被static修饰的属性。作用范围在类变量定义之后。

class Person{

//成员变量,实例变量

String name;

//静态变量,类变量,所有对象共享的属性用static修饰

static String country="CN";

public void show(){

System. out.println(country+":"+ name);}}

class StaticDemo{

public static void main(String[]args){

Personp= new Person();

System. out.println(p.country);//可以用类名直接调用

System. out.println(Person.country);}}

扩展资料:

成员变量和类变量的区别:

1、两个变量的生命周期不同

成员变量随着对象的创建而存在,随着对象的回收而释放。

静态变量随着类的加载而存在,随着类的消失而消失。

2、调用方式不同

成员变量只能被对象调用。

静态变量可以被对象调用,还可以被类名调用。

3、数据存储位置不同

成员变量存储在堆内存的对象中,所以也叫对象的特有数据。

静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据。

关于本次成员变量[详解]和JAVA 类变量和成员变量怎么理解的问题分享到这里就结束了,如果解决了您的问题,我们非常高兴。