《侯老师C++面向对象开发》从入门到“入土”
Posted 旧势力底层学习者
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《侯老师C++面向对象开发》从入门到“入土”相关的知识,希望对你有一定的参考价值。
目录
一、需要的语言基础
1.1变量(Variables)
变量其实只不过是程序可操作的存储区的名称。C++ 中每个变量都有指定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。
变量的名称可以由字母、数字和下划线字符组成。它必须以字母或下划线开头。大写字母和小写字母是不同的,因为 C++ 是大小写敏感的。
1.2类型(types)
bool:存储值为true/false
int main()
bool a = true; //这里可以自己尝试改成数字,会提示Error,自动变更为true
std::cout << a << std::endl;
return 0;
char:通常为字符(8位),整数类型
//这里比较明确的将声明与赋值拆开来,运行后结果为s
//这里要理解的是char存储标准ASCII标准字符,115为s
int main()
char a;
a = 115;
std::cout << a << std::endl;
return 0;
int:定义一个整型变量
//这里注意的是对int而言,其大小为32位有符号整型
int main()
int a = 1234567890;
std::cout << a << std::endl;
return 0;
这里只写了三个最基础的,建议自己进行实际操作进行理解,除此以外还有:
float(单精度浮点值。1位符号,8位指数,23位小数)
sign(1bit)---exponent(8bits)---fraction(23bits)
double(双精度浮点值。双精度是1位符号,11位指数,52位小数。)
sign(1bit)---exponent(11bits)---fraction(52bits)
sturce、void、wchar_t等等。C++ 也允许定义各种其他类型的变量,比如枚举、指针、数组、引用、数据结构、类等等
1.3作用域(scope)
字面理解作用域是一个区域,也就是代码区域,一般来说有变量根据作用域不同可以分为三种,也可以说分三个地方可以定义变量。
· 在函数或某个代码块内部声明的变量,称为局部变量。
· 在函数参数定义中声明的变量,称为形式参数(形参)。
· 在所有函数外部(include之后定义)声明的变量,称为全局变量。
Tips:注意在函数内部即main中的局部变量与全局变量名相同时,输出为main中局部变量值,全局变量值会被覆盖。
#include<iostream>
using namespace std;
int g = 10; //全局变量声明
int main()
int b = 10; //局部变量声明
int g = 20; //可以自己注释看一下Tips效果
cout << a << endl;
cout << a << endl;
return 0;
全局变量时,系统会自动初始化为下列值(可以试试Clion/Vscode的提示就明白了):
int | 0 |
char | '\\0' |
float | 0 |
double | 0 |
pointer | NULL |
1.4循环(loops)
while:只要给定的条件为真,while 循环语句会重复statement语句。可以理解为当符合condition条件时,就一直循环执行statement语句。condition可以为任意表达式,任意非0为真,statement()可以是单独的执行语句或者几个执行模块。
while(condition)
statement();
除此之外还有for循环、do---while循环、嵌套循环(字面理解套娃结构//狗头//)
对循环的打断或者说控制语句包括熟知的break、continue、goto。
1.5流程控制/判断(judge)
这里说流程控制,我更习惯称为判断(judge),包含if、if---else、嵌套if、switch、嵌套switch。
// 这里以if---else为例
#include <iostream>
using namespace std;
int main()
int a ;
cout << "请输入a的值,a=" << endl;
cin >> a;
int b;
if (a > 20)
cout << a << endl;
else
b = a - 5;
cout << b << endl;
return 0;
1.6、了解程序需要编译、连接才可运行
1.7、使用工具进行编译(避免shell型)
常用的Vim(Linux命令行改一个小东西还是常用的)、Vscode、Pycharm、CLion等等。
二、程序的基础分类
Object Basde(基于对象)
--class without pointer members(无指针)
----Complex
--class with pointer members(有指针)
----String
Object Orented(面向对象)
--继承(inheritance)
--复合(composition)
--委托(delegation)
面向对象程序设计(Object-Oriented Programming,简称OOP)
三、Download课件
(可在Github中搜索自行下载,搜索关键字自行百度)
complex.h
complex-test.cpp
string.h
string-test.cpp
oop-demo.h
oop-test.cpp
四、C++历史与演化
4.1 C++历史演化
· B语言(1969)
· C语言(1972)
· C++语言(1983)(new C ---> C with Class ---> C++)
· Java语言
· C#语言
当然除C++外还有我们熟知的Python、JavaScript。
4.2 C++版本更替
C++ 98 (1.0)
C++ 03 (TR1,Technical Report 1)
C++ 11 (2.0)
C++ 14
C++ 17
C++ 20
这里我们要说的是不仅仅关注C++这门语言,更要关心C++的STL(标准库)。
STL英文全称 standard template library,中文可译为标准模板库或者泛型库,其包含有大量的模板类和模板函数,是 C++ 提供的一个基础模板的集合,用于完成诸如输入/输出、数学计算等功能。
五、相关书籍
C++ Primer
C++ Primer Plus(正在看)
Effiective C++
STL源码剖析
六、std::cout与cout的基本区别
以1.5流程控制/判断(judge)为例
std是C++的标准库(STL) 命名空间, C++标准里的所有函数,类等都定义在std里。
示例一、
如果在程序开头未有"using namespace std;"那么在下列程序中就是这么写,表明在程序每次cout时都要对其进行说明(::作用域运算符)
#include <iostream>
int main()
int a ;
std::cout << "请输入a的值,a=" << std::endl;
std::cin >> a;
int b;
if (a > 20)
std::cout << a <<std::endl;
else
b = a - 5;
std::cout<< b <<std::endl;
return 0;
示例二、
在程序开头加入"using namespace std;"后,等同于在下方程序中默认使用std的标准库命名空间,实际上与示例一没有区别。
#include <iostream>
using namespace std;
int main()
int a ;
cout << "请输入a的值,a=" << endl;
cin >> a;
int b;
if (a > 20)
cout << a << endl;
else
b = a - 5;
cout << b << endl;
return 0;
下一章:(二)C++头文件与类的声明
C++从入门到入土第二篇:类和对象基础
系列文章目录
文章目录
前言
上一篇我们了解了C++基础入门知识,现在让我们一起来探索最具魅力的面向对象。一、类和对象的基本概念
结构化程序设计
C语言使用结构化程序设计:
程序 = 数据结构 + 算法
- 程序由全局变量以及众多相互调用的函数组成 。
- 算法以函数的形式实现,用于对数据结构进行操作。
结构化程序设计的不足
- 结构化程序设计中,函数和其所操作的数据结构,没有直观的联系。
- 随着程序规模的增加,程序逐渐难以理解,很难一下子看出来:
- 某个数据结构到底有哪些函数可以对它进行操作?
- 某个函数到底是用来操作哪些数据结构的?
- 任何两个函数之间存在怎样的调用关系?
- 结构化程序设计没有“封装”和“隐藏”的概念。 要访问某个数据结构中的某个变量,就可以直接访问,那么当该变量的定义有改动的时候,就要把所有访问该变量的语句找出来修改,十分不利于程序的维护、扩充。
- 难以查错,当某个数据结构的值不正确时,难以找出到底是那个函数导致的。
- 重用:在编写某个程序时,发现其需要的某项功 能,在现有的某个程序里已经有了相同或类似的 实现,那么自然希望能够将那部分代码抽取出来, 在新程序中使用。
- 在结构化程序设计中,随着程序规模的增大,由 于程序大量函数、变量之间的关系错综复杂,要抽取这部分代码,会变得十分困难。
总之,结构化的程序,在规模庞大时,会变得难以理解,难以扩充(增加新功能),难以查错,难以重用。
- 如何更高效地实现函数的复用?
- 如何更清晰的实现变量和函数的关系?使得程序 更清晰更易于修改和维护。
面向对象的程序设计
面向对象的程序 = 类 + 类 + …+ 类
面向对象的程序设计方法:
- 将某类客观事物共同特点(属性)归纳出来,形成一个数据 结构(可以用多个变量描述事物的属性);
- 将这类事物所能进行的行为也归纳出来,形成一个个函数, 这些函数可以用来操作数据结构(这一步叫“抽象”)。
- 然后,通过某种语法形式,将数据结构和操作该数据结构的函 数“捆绑”在一起,形成一个“类”,从而使得数据结构和操作该数据结构的算法呈现出显而易见的紧密关系,这就是“封装”。
- 面向对象的程序设计具有“抽象”,“封装”“继承”“多态” 四个基本特点。
例如:
将长、宽变量和设置长,宽,求面积,以及求周长的三个函数“封装”在一起,就能形成一个“矩形类”。
长、宽变量成为该“矩形类”的“成员变量”,三个函数成为该类的“成员函数” 。 成员变量和成员函数统称为类的成员。
class CRectangle
{
public:
int w, h;
int Area() {
return w * h;
}
int Perimeter(){
return 2 * ( w + h);
}
void Init( int w_,int h_ ) {
w = w_; h = h_;
}
}; // 必须有分号
通过类,可以定义变量。类定义出来的变量,也称为类的实例,就是我们所说的“对象” 。
C++中,类的名字就是用户自定义的类型的名字。可以象使用基本类型那样来使用它。CRectangle 就是一种用户自定义的类型。
int main( )
{
int w,h;
CRectangle r; //r 是一个对象
cin >> w >> h;
r.Init( w,h);
cout << r.Area() << endl
<<r.Perimeter();
return 0;
}
对象的内存分配
类的内存分配与结构体分配内存相同,类的成员函数所占内存不属于类。
每个对象各有自己的存储空间。一个对象的某个成员变量被改变了,不会影响到另一个对象。
和结构变量一样,对象之间可以用 “=”进行赋值,但是不能用 “==”,“!=”,“>”,“<”“>=”“<=”进行比较,除非这些运算符经过了“重载”(第四篇讲)。
使用类的成员变量和成员函数
用法1:对象名.成员名
CRectangle r1,r2;
r1.w = 5;
r2.Init(5,4);
用法2: 指针->成员名
CRectangle r1,r2;
CRectangle * p1 = & r1;
CRectangle * p2 = & r2;
p1->w = 5;
p2->Init(5,4); //Init作用在p2指向的对象上
用法3:引用名.成员名
CRectangle r2;
CRectangle & rr = r2;
rr.w = 5;
rr.Init(5,4); //rr 的值变了,r2 的值也变
二、类和对象基础
类成员的可访问范围
在类的定义中,用下列访问范围关键字来说明类成员
可被访问的范围:
private: 私有成员,只能在成员函数内访问
public : 公有成员,可以在任何地方访问
protected: 保护成员,后面再说,暂时理解和私有成员类似
以上三种关键字出现的次数和先后次序都没有限制。
代码如下(示例):
class className {
private:
私有属性和函数
public:
公有属性和函数
protected:
保护属性和函数
};
如过某个成员前面没有上述关键字,则缺省地被认为是私有成员。
class Man {
int nAge; // 私有成员
char szName[20]; // 私有成员
public:
void SetName(char * szName){
strcpy( Man::szName,szName);
}
};
在类的成员函数内部,能够访问:
- 当前对象的全部属性、函数;
- 同类其它对象的全部属性、函数。
在类的成员函数以外的地方,只能够访问该类对象的公有成员。
成员函数在类内声明,一般在类外实现(也可类内声明并实现),需要加上“类名::”。
class CEmployee {
private:
char szName[30]; // 名字
public :
int salary; // 工资
void setName(char * name);
void getName(char * name);
void averageSalary(CEmployee e1,CEmployee e2);
};
void CEmployee::setName( char * name) {
strcpy( szName, name); //ok
}
void CEmployee::getName( char * name) {
strcpy( name,szName); //ok
}
void CEmployee::averageSalary(CEmployee e1,CEmployee e2){
cout << e1.szName; //ok ,访问同类其他对象私有成员
salary = (e1.salary + e2.salary )/2;
}
int main()
{
CEmployee e;
strcpy(e.szName,"Tom1234567889"); // 编译错,不能访问私有成员
e.setName( "Tom"); // ok
e.salary = 5000; //ok
return 0;
}
设置私有成员的机制,叫“隐藏”
- “隐藏”的目的是强制对成员变量的访问一定要通过成员函数 进行,那么后成员变量的类型等属性修改后,只需要更改成员函数即可。否则,所有直接访问成员变量的语句都需要修改。
同样我们也可以用struct定义类
struct CEmployee {
char szName[30]; // 公有!!
public :
int salary; // 工资
void setName(char * name);
void getName(char * name);
void averageSalary(CEmployee
e1,CEmployee e2);
};
和用"class"的唯一区别,就是未说明是公有,还是私有的成员。
成员函数的重载及参数缺省
- 成员函数也可以重载
- 成员函数可以带缺省参数。
注:使用缺省参数要注意避免有函数重载时的二义性
构造函数(constructor)
基本概念
成员函数的一种
名字与类名相同,可以有参数,不能有返回值(void也不行)
作用是对对象进行初始化,如给成员变量赋初值
如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数
默认构造函数无参数,不做任何操作。
- 如果定义了构造函数,则编译器不生成默认的无参数的构造函数
- 对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数
- 一个类可以有多个构造函数
为什么需要构造函数:
- 构造函数执行必要的初始化工作,有了构造函数,就不 必专门再写初始化函数,也不用担心忘记调用初始化函数。
- 有时对象没被初始化就使用,会导致程序出错。对象不初始化是件很糟糕的事。
代码如下(示例):
class Complex {
private :
double real, imag;
public:
Complex( double r, double i = 0);
};
Complex::Complex( double r, double i) {
real = r; imag = i;
}
int main()
{
Complex c1; // error, 缺少构造函数的参数
Complex * pc = new Complex; // error, 没有参数
Complex c1(2); // OK
Complex c1(2,4), c2(3,5);
Complex * pc = new Complex(3,4);
return 0;
}
同样的构造函数也可以根据需求重载。
构造函数最好是public的,private构造函数不能直接用来初始化对象
构造函数在数组中的使用
class Test {
public:
Test( int n) { } //(1)
Test( int n, int m) { } //(2)
Test() { } //(3)
};
Test array1[3] = { 1, Test(1,2) };// 三个元素分别用(1),(2),(3)初始化
Test * pArray[3] = { new Test(4), new Test(1,2) };//两个元素分别用(1),(2) 初始化
拷贝(复制)构造函数
- 只有一个参数,即对同类对象的引用。 形如 X::X( X& )或X::X(const X &), 二者选一 后者能以常量对象作为参数
- 如果没有定义拷贝构造函数,那么编译器生成默认拷贝构造函数。默认的拷贝构造函数完成复制功能。
如果定义的自己的拷贝构造函数,则默认的拷贝构造函数不存在。
class Complex {
public :
double real,imag;
Complex(){ }
Complex( const Complex & c ) {
real = c.real;
imag = c.imag;
cout << “Copy Constructor called”;
}
};
Complex c1;
Complex c2(c1);//调用自己定义的拷贝构造函数,输出 Copy Constructor called
拷贝构造函数起作用的三种情况
- 当用一个对象去初始化同类的另一个对象时。
Complex c2(c1);
Complex c2 = c1; //初始化语句,非赋值语句
- 如果某函数有一个参数是类 A 的对象, 那么该函数被调用时,类A的拷贝构造函数将被调用。
class A
{
public:
A() { };
A( A & a) {
cout << "Copy constructor called" <<endl;
}
};
void Func(A a1){ }
int main(){
A a2;
Func(a2);
return 0;
}
程序输出结果为: Copy constructor called
- 如果函数的返回值是类A的对象时,则函数返回时, A的拷贝构造函数被调用:
class A
{
public:
int v;
A(int n) { v = n; };
A( const A & a) {
v = a.v;
cout << "Copy constructor called" <<endl;
}
};
A Func() {
A b(4);
return b;
}
int main() {
cout << Func().v << endl; return 0;
}
输出结果:
Copy constructor called
4
注意:对象间赋值并不导致拷贝构造函数被调用
类型转换构造函数
- 定义转换构造函数的目的是实现类型的自动转换。
- 只有一个参数,而且不是拷贝构造函数的构造函数,一般就可以看作是转换构造函数。
- 当需要的时候,编译系统会自动调用转换构造函数,建立 一个无名的临时对象(或临时变量)。
//Comlex是一个类
Complex( int i) {// 类型转换构造函数
cout << "IntConstructor called" << endl;
real = i; imag = 0;
}
析构函数(destructors)
- 名字与类名相同,在前面加‘~’, 没有参数和返回值,一个类最多只能有一个析构函数。
- 析构函数对象消亡时即自动被调用。可以定义析构函数来在 对象消亡前做善后工作,比如释放分配的空间等
- 如果定义类时没写析构函数,则编译器生成缺省析构函数。 缺省析构函数什么也不做。
- 如果定义了析构函数,则编译器不生成缺省析构函数。
class String{
private :
char * p;
public:
String () {
p = new char[10];
}
~ String () ;
};
String::~ String()
{
delete [] p;
}
对象数组生命期结束时,对象数组的每个元素的析构函数都会被调用。
delete 运算导致析构函数调用。
Ctest * pTest;
pTest = new Ctest; // 构造函数调用
delete pTest; // 析构函数调用
pTest = new Ctest[3]; // 构造函数调用3次 次
delete [] pTest; // 析构函数调用3次
一旦有对象生成调用构造函数
一旦有对象消亡调用析构函数
总结
以上就是今天要讲的内容,类和对象基础,难点在于如何包装一个类,以及构造函数和析构函数的调用次序,希望你能对类和对象有更深的理解。感谢你的阅读,欢迎点赞关注,我会持续更新。以上是关于《侯老师C++面向对象开发》从入门到“入土”的主要内容,如果未能解决你的问题,请参考以下文章