福甭市建设局网站,抚顺网站建设费用,淘宝网站SEO怎么做,赣州小程序开发公司本鼠浅浅介绍一些C类和对象的知识#xff0c;希望能得到读者老爷们的垂阅#xff01; 目录
1.面向过程和面向对象
2.类的引入
3.类的定义
4.类的访问限定符及封装
4.1.类的访问限定符
4.2.封装
5.C中struct和class的区别
6.类域
7.类的实例化
8.类对象模型
8.1.类…本鼠浅浅介绍一些C类和对象的知识希望能得到读者老爷们的垂阅 目录
1.面向过程和面向对象
2.类的引入
3.类的定义
4.类的访问限定符及封装
4.1.类的访问限定符
4.2.封装
5.C中struct和class的区别
6.类域
7.类的实例化
8.类对象模型
8.1.类对象大小
8.2.类对象的存储方式
9.this指针
9.1.this指针的引出 9.2.this指针的特性
10.Question 1.面向过程和面向对象
作为了解
C语言是面向过程的关注的是过程分析出求解问题的步骤通过函数调用逐步解决问题。
C是基于面向对象的关注的是对象将一件事情拆分成不同的对象靠对象之间的交互完 成。
没了介绍完了。其实对于解决问题C语言关注的是解决问题的动作而C关注的是解决问题的对象呗。
2.类的引入
俺们都知道C语言中有结构体的概念。那么C兼容C语言的大部分语法所以C也可以使用结构体。但是C语言结构体中只能定义变量在C中结构体内不仅可以定义变量也可以定义函数。
举个栗子给老爷们瞅瞅
#includeiostream
#includeassert.h
using namespace std;
typedef int SLTDateType;
struct SListNode
{SLTDateType data;struct SListNode* next;//动态申请一个节点SListNode* BuyNode(SLTDateType x){SListNode* newnode (SListNode*)malloc(sizeof(SListNode));if (newnode NULL){perror(malloc fail);exit(-1);}newnode-data x;newnode-next NULL;return newnode;}//单链表打印void Print(SListNode* plist){SListNode* cur plist;while (cur ! NULL){cout cur-data -;cur cur-next;}cout nullptr endl;}//单链表尾插void PushBack(SListNode** pplist, SLTDateType x){assert(pplist);if (*pplist NULL)//单链表为空{*pplist BuyNode(x);}else//单链表不为空{SListNode* tail *pplist;while (tail-next ! NULL)//找尾{tail tail-next;}SListNode* newnode BuyNode(x);tail-next newnode;}}//单链表头插void PushFront(SListNode** pplist, SLTDateType x){assert(pplist);SListNode* newnode BuyNode(x);newnode-next *pplist;*pplist newnode;}//销毁单链表void Destroy(SListNode** pphead){assert(pphead);SListNode* cur *pphead;while (cur){SListNode* next cur-next;free(cur);cur next;}*pphead NULL;}
};
int main()
{SListNode* sl nullptr;sl-PushBack(sl, 0);sl-PushFront(sl, -1);sl-Print(sl);sl-Destroy(sl);return 0;
} 是可以运行的且结果符合预期
上面结构体的定义在C中更喜欢用class来代替struct。这样C中结构体就升级成了类。
3.类的定义 类的定义格式class为定义类的关键字ClassName为类的名字(可随意变换){}中为类的主体注意类定义结束时后面分号不能省略。
这样子
class className//className是类的名字
{// 类体由成员函数和成员变量组成}; // 一定要注意后面的分号
类体中内容称为类的成员类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。 类的两种定义方式
1.方式一声明和定义全部放在类体中。
需注意成员函数如果在类中定义编译器可能会将其当成内 联函数处理。
像这样子
class person
{//成员变量的声明int age;const char* name;const char* sex;//成员函数的定义void showinformation(){cout name - sex - age endl;}
}; 2.方式二类声明放在.h文件中成员函数定义放在.cpp文件中。其实就是成员函数声明和定义分离。
注意成员函数名前需要加类名::
像这样子
类的.h文件假如头文件为Person.h
class person
{//成员变量的声明int age;const char* name;const char* sex;//成员函数的声明void showinformation();
};
类的.cpp文件
#includePerson.h//成员函数的定义void person::showinformation(){cout name - sex - age endl;}
一般情况下更期望采用第二种方式。
4.类的访问限定符及封装
4.1.类的访问限定符
C实现封装的方式用类将对象的属性与方法结合在一块让对象更加完善咱们看类可以有成员变量和成员函数分别对应属性与方法。通过访问权限选择性的将其接口提供给外部的用户使用。
类的访问限定符有三种public公有、protected保护、private私有。
类的访问限定符体现了访问权限
public修饰的成员在类外可以直接被访问。protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)。访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。如果后面没有访问限定符作用域就到 } 即类结束。class的默认访问权限为privatestruct为public(因为struct要兼容C)。
注意访问限定符只在编译时有用当数据映射到内存后没有任何访问限定符上的区别。 举一些栗子来证实访问限定符的作用
#includeiostream
using namespace std;
class person
{int _age;const char* _name;const char* _sex;void showinformation(){cout _name -_sex - _age endl;}
};
int main()
{person HD;HD._age 20;//error C2248: “person::_age”: 无法访问 private 成员(在“person”类中声明)return 0;
}主函数第二条语句编译不通过是因为 class的默认访问权限为privateprivate修饰的成员在类外不能直接被访问。
如果我们改用public修饰该类的所有成员
#includeiostream
using namespace std;
class person
{
public:int _age;const char* _name;const char* _sex;void showinformation(){cout _name -_sex - _age endl;}
};
int main()
{person HD;HD._age 20;HD._name HD;HD._sex male;HD.showinformation();return 0;
}编译通过运行成功!
又如果
#includeiostream
using namespace std;
class person
{
public:int _age;const char* _name;const char* _sex;
private:void showinformation(){cout _name -_sex - _age endl;}
};
int main()
{person HD;HD._age 20;HD._name HD;HD._sex male;HD.showinformation();//error C2248: “person::showinformation”: 无法访问 private 成员(在“person”类中声明)return 0;
} 主函数第五条语句编译报错。因为该类的成员函数被private修饰。第二条到第四条语句编译通过是因为该类的所有成员变量被public修饰。访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
4.2.封装
面向对象的三大特性封装、继承、多态。
封装封装本质是一种管控。将数据和操作数据的方法进行有机结合隐藏对象的属性和实现细节仅对外公开接口来和对象进行交互。
在C语言中实现封装可以通过类将数据以及操作数据的方法进行有机结合通过访问权限来 隐藏对象内部实现细节控制哪些方法可以在类外部直接被使用。
5.C中struct和class的区别
C需要兼容C语言所以C中struct可以当成结构体使用。另外C中struct还可以用来定义类和class定义类是一样的区别是struct定义的类默认访问权限是publicclass定义的类 默认访问权限是private。在继承和模板参数列表位置struct和class也有区别本鼠以后再介绍因为本鼠也不清楚捏
6.类域
类定义了一个新的作用域类域类的所有成员都在类的作用域中。与命名空间域一样类域也只会影响访问所以在类体外定义成员时需要使用 :: 作用域限定符指明成员属于哪个类域。
举例请看类的定义方式二。
其实类的定义方式二中的成员函数的声明和定义分离写到同一个.cpp文件下面也行像这样子
#includeiostream
using namespace std;
class person
{
public:int _age;const char* _name;const char* _sex;void showinformation();
};
void person::showinformation()
{cout _name - _sex - _age endl;
}
int main()
{person HD;HD._age 20;HD._name HD;HD._sex male;HD.showinformation();return 0;
} 编译也通过运行成功。
不过既然都写到同一个文件下了成员函数的声明和定义分开写好像有种脱裤子放屁的无力感没必要啊。。。 直接写成类的定义方式一不就好了。。。
7.类的实例化
类是对对象进行描述的是一个模型一样的东西限定了类有哪些成员定义出一个类并没有分配实际的内存空间来存储它。所以要让类有使用价值就要将类实例化。
用类类型创建对象的过程称为类的实例化。说白了类的实例化就是创建类型为该类的对象呗。像这样子
#includeiostream
using namespace std;
class person
{
public:int _age;const char* _name;const char* _sex;void showinformation();
};
void person::showinformation()
{cout _name - _sex - _age endl;
}
int main()
{person HD;//HD是person类实例化的对象return 0;
} 定义出一个类并没有分配实际的内存空间来存储它如上代码定义出person类但是没有给person类开空间的。用person类类型创建对象HD就是person类实例化对象HD是开了空间来存储的。
一个类可以实例化出多个对象很简单易懂吧就像这样
#includeiostream
using namespace std;
class person
{
public:int _age;const char* _name;const char* _sex;void showinformation();
};
void person::showinformation()
{cout _name - _sex - _age endl;
}
int main()
{person HD;//HD是person类实例化的对象person LCD;//LCD也是person类实例化的对象return 0;
}
HD和LCD都是person类实例化对象 。
实例化出的对象占用实际的物理空间存储类成员变量我们看
#includeiostream
using namespace std;
class person
{
public:int _age;const char* _name;const char* _sex;void showinformation();
};
void person::showinformation()
{cout _name - _sex - _age endl;
}
int main()
{person._age 10;//error C2059: 语法错误:“.”return 0;
}
编译报错是因为person类是没有空间的只有person类实例化出的对象才有具体的年龄。person类中的成员变量如_age只是声明没有开空间来存储成员变量只有person类实例化对象才有空间存储类成员变量如_age。
最后提一嘴类对象实例化的时候代码可以带上class或者struct也可以不带一般不会带上。比如这样子写也能编译通过运行成功
#includeiostream
using namespace std;
class person
{
public:int _age;const char* _name;const char* _sex;void showinformation(){cout _name - _sex - _age endl;}
};
int main()
{class person HD;HD._age 20;HD._name HD;HD._sex male;HD.showinformation();return 0;
} 8.类对象模型
8.1.类对象大小
我们也许会有疑问C语言的结构体成员可不能有函数计算结构体变量的大小遵循结构体内存对齐呗。但是类定义时可以包含成员变量和成员函数那么类实例化对象的大小占多少字节该如何计算捏
答案就是一个类实例化对象的大小(或者说类的大小)实际就是该类中”成员变量”之和当然要注意内存对齐。就是说不用管类成员函数的存在。为什么这样子本鼠下边再解释。
#includeiostream
using namespace std;
class rectangle
{
public:double _long;double _wide;double _high;void showinformation(){cout _long - _wide - _high endl;}
};
int main()
{rectangle n1;cout sizeof(rectangle) endl;cout sizeof(n1);return 0;
} 额sizeof(类)和sizeof(类实例化对象)是同一个意思都是计算类实例化对象多大。
附上结构体内存对齐规则
1. 第一个成员在与结构体偏移量为0的地址处。2. 其他成员变量要对齐到某个数字对齐数的整数倍的地址处。 注意对齐数 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为83. 结构体总大小为最大对齐数所有变量类型最大者与默认对齐参数取最小的整数倍。4. 如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整 体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍。
注意空类的大小空类比较特殊编译器给了空类一个字节来唯一标识这个类的对象。如果空类的大小为0个字节那么该类实例化对象如何证明存在过捏
#includeiostream
using namespace std;
class N
{
};
int main()
{N n1;cout sizeof(N) endl;cout sizeof(n1);return 0;
} 好了为啥子一个类实例化对象的大小(或者说类的大小)实际就是该类中”成员变量”之和当然要注意内存对齐。就是说不用管类成员函数的存在。请看类对象的存储方式
8.2.类对象的存储方式
拿这个代码来说
#includeiostream
using namespace std;
class person
{
public:int _age;const char* _name;const char* _sex;void showinformation(){cout _name - _sex - _age endl;}
};
int main()
{class person HD;person LCD;return 0;
}
person类实例化对象有两个HD和LCD。这两个对象个中成员变量是不同的需要存储不同的属性所以说是不同的但是调用同一份函数类方法是一样的。当一 个类创建多个对象时如果每个对象中都会保存一份代码相同代码保存多次浪费空间。
那么计算机的做法是当一个类创建多个对象时对象只保存成员变量成员函数存放在公共代码段区。
就是说当类实例化对象时只会给该对象开遵循结构体内存对齐的“成员变量”之和的内存空间当该对象需要用到成员函数时去公共代码段调用。 9.this指针
9.1.this指针的引出
看一个代码
#includeiostream
using namespace std;
class Date
{int _year; // 年int _month;// 月int _day;// 日
public:void Init(int year, int month, int day){_year year;_month month;_day day;}void Print(){cout _year - _month - _day endl;}
};
int main()
{Date d1, d2;d1.Init(2024, 6, 4);d2.Init(2003, 12, 12);d1.Print();d2.Print();return 0;
} 运行没毛病但是有一个问题 Date类中有 Init 与 Print 两个成员函数函数体中没有关于不同对象的区分。那当d1调用 Init 函 数时该函数是如何知道应该设置d1对象而不是设置d2对象呢
C中通过引入this指针解决该问题其实即C编译器给每个“非静态的成员函数“增加了一个隐藏 的指针参数让该指针指向当前对象(函数运行时调用该函数的对象)在函数体中所有“成员变量” 的操作都是通过该指针去访问。只不过所有的操作对用户是透明的即用户不需要来传递编译器自动完成。this指针是每个“非静态的成员函数”的第一个参数而且是隐藏起来的。 this指针是隐藏起来的拿上面代码来说当d1调用Init函数时传递的实参不仅仅有明面上的2024、6和4还有该对象的地址即d1的地址而且第一个传递d1的地址。 Init函数有一个隐藏的this指针用来接收d1的地址所以Init函数知道该设置d1对象而不是d2对象。 9.2.this指针的特性
用本代码举例
#includeiostream
using namespace std;
class Date
{int _year; // 年int _month;// 月int _day;// 日
public:void Init(int year, int month, int day){_year year;_month month;_day day;}void Print(){cout _year - _month - _day endl;}
};
int main()
{Date d1, d2;d1.Init(2024, 6, 4);d2.Init(2003, 12, 12);d1.Print();d2.Print();return 0;
}
this指针的类型类类型* const。即成员函数中不能给this指针赋值。比如上面代码Init函数隐藏的this指针类型是Data* const。在实参或者形参的位置上不能将this指针显示写但是函数内部可以使用。
请看
#includeiostream
using namespace std;
class Date
{int _year; // 年int _month;// 月int _day;// 日
public:void Init(Date const* this, int year, int month, int day){_year year;_month month;_day day;}void Print(Data const*this){cout _year - _month - _day endl;}
};
int main()
{Date d1, d2;d1.Init(d1, 2024, 6, 4);d2.Init(d2, 2003, 12, 12);d1.Print(d1);d2.Print(d2);return 0;
}
编译报错因为将形参位置隐藏的this指针显示写出来了而且实参位置传入d1地址或者d2地址给this指针。这些都是编译器完成的我们不应该显示写出来。
再看
#includeiostream
using namespace std;
class Date
{int _year; // 年int _month;// 月int _day;// 日
public:void Init( int year, int month, int day){this-_year year;this-_month month;this-_day day;}void Print(){cout this-_year - this-_month - this- _day endl;}
};
int main()
{Date d1, d2;d1.Init( 2024, 6, 4);d2.Init( 2003, 12, 12);d1.Print();d2.Print();return 0;
}
编译通过运行成功!函数内部可以使用隐藏的this指针。详情请看Init函数或者Print函数内部。
this指针本质上是“成员函数”的形参当对象调用成员函数时将对象地址作为实参传递给this形参。所以对象中不存储this指针。 this指针是“成员函数”第一个隐藏的指针形参一般情况由编译器通过ecx寄存器自动传递不需要用户传递。
呃呃呃this指针存储在栈区有些编译器用寄存器存储因为它是一个形参。
10.Question
1.下面程序编译运行结果是 A、编译报错 B、运行崩溃 C、正常运行
#includeiostream
using namespace std;
class A
{
public:void Print(){cout Print() endl;}
private:int _a;
};
int main()
{A* p nullptr;p-Print();return 0;
}
也许你会跟本鼠一样看到空指针就想选A。但是正确答案是C。因为p虽然是空指针但是Print函数并不是通过解引用p找到的因为Print函数存储在公共代码段中而不是存储在p指向的空间里。 看看结果如图 2.下面程序编译运行结果是 A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:void PrintA() {cout_aendl;}
private:int _a;
};
int main()
{A* p nullptr;p-PrintA();return 0;
}哈哈哈答案选B。因为PrintA函数会将地址p作为实参传递给PrintA函数隐藏的形参this指针。我们看PrintA函数内部需要用到this指针解引用的哦this指针接收到的值是空指针而且成员变量_a是存储在this指向的空间里面的成员变量存储在类实例化对象里面当然会崩溃了。
崩溃了
感谢阅读