《后台开发-核心技术与应用实践》
2019-03-20 本文已影响0人
Peggy_623
作者按:我一般都是野路子学的C++,没有系统的学习和使用过,现在补充。
1、C++编程常用技术
1.1 第一个C++程序
- 预处理语句=>#include
- <iostream.h>&<iostream>、#include<>&#include ""
- <string> & <string.h>
1.2 函数
- 函数的定义
- C++面向对象的程序设计中,主函数以外的函数大多被封装在类中。主函数或其他函数可以通过类对象调用类中的函数。
- 形参和实参的区别是:形参只是被调用时才分配内存单元,在调用结束时,立即释放所分配的内存单元。
- 函数重载
- C++允许用同一函数名定义多个函数,但这些函数必须参数个数不同或参数类型不同。
- 函数模板
- 建立一个通用函数,其函数类型和形参不具体指定,而是用一个虚拟的类型来代表,这个通用函数就是函数模板。凡是函数体相同的函数都可以用这个模版来带滴,而不用定义多个函数。
- 在调用函数时,系统会根据实参的类型来取代模板中的虚拟类型。
- 用函数模板比函数重载更方便,但它只适用于函数个数相同而类型不同的情况。
#include <iostream>
using namespace std;
template <typename T>
T min(T a, T b, T c)
{
if(a>b) a = b;
if(a>c) a = c;
return a;
}
int main()
{
int a = 1, b = 2, c = 3;
cout << min(a, b, c) << endl;
long long a1 = 100000000;
long long b1 = 200000000;
long long c1 = 300000000;
cout << min(a, b, c) << endl;
return 0;
}
1.3 数组
- 数组的定义
- 数组时相同类型数据的集合。
- 一个数组在内存中占用的存储单元是连续的,也就是说一个数组在内存中占用一片连续的存储单元。
char str[10] = "book";
strlen(str) = 4
sizeof(str) = 10
- strlen和sizeof的区别
1⃣️strlen是函数,在运行时才能计算,参数必须是字符型指针(char*),且必须是以'\0'
结尾。功能是返回字符串的长度。
2⃣️sizeof()是运算符,在编译时就计算好了。功能是计算数据空间的字节数。sizeof常用语返回类型和静态分配的对像、结构或数组占用的空间,返回值和对象、结构或数组所存储的内容没有关系。
1.4 指针
- 指针的概念
- 内存单元的地址和内存单元的内容。
- 数组与指针
- 在C++中,数组名代表数组第一个元素的地址。
- 数组指针/指针数组:数组指针指示一个指针变量,可以认为是C语言里专门用来指向二维数组的,它占用内存中一个指针的存储空间;指针数组是多个指针变量,以数组形式存在内存当中,占用多个指针的存储空间.
int (*p)[n]
int* p[n]
优先级:() > [] > *
- 字符串与指针
字符数组/字符指针/字符指针数组/字符串变量
#include<iostream>
#include<string>
using namespace std;
int main()
{
// 字符数组
char str[] = "I am a programmer.";
// 字符指针
char* str1 = "qbc";
// 字符指针数组
char* str2 = {"hello world", "good bye"};
// 字符串变量
string str3 = "I am a programmer, too.";
return 0;
}
- 函数与指针
- 函数指针是指向函数的指针变量。所以,函数指针首先是个指针变量,而且这个变量指向一个函数。
- 函数指针的声明方法
// 返回类型 (*指针变量名)([形参列表])
#include<iostream>
using namespace std;
int Mmin(int x, int y)
{
if(x > y) x = y;
return x;
}
int Mmax(int x, int y)
{
if(x<y) return y;
return x;
}
int mian()
{
int (*f) (int x, int y);
int a = 10, b = 20;
f = Mnin;
cout << (*f)(a, b) << endl;
f = Mmax;
cout << (*f)(a, b) << endl;
return 0;
}
1.5 引用
- 引用的定义
- 引用是一种变量类型,它用于为一个变量起一个别名。
- 引用作为参数
- 引用一个重要的作用就是作为函数的参数。
类型标识符 &引用名 = 目标变量名
int a = 10;
int &r = a ;
- 常引用
- 引用型参数应该在能被定义为const的情况下,尽量定义为const。
const 类型标识符 &引用名 = 目标变量名
1.6 结构体、公用体、枚举
1.6.1 结构体、共同体、枚举的概念
- 结构体/共用体/枚举
1.6.2 结构体、共用体在内存单元占用字节数计算
- union中变量共用内存,应以最长的成员变量占用的字节数为准,同时又要满足共用体内变量的默认内存对齐方式,必须以最长的数据类型占用的字节数对其。
- struct中每个变量分开占用空间,且要遵循最后结构体占用的字节数是8的倍数以对齐字节。
1.7 预处理
- 宏定义/文件包含/条件编译/布局控制
- 常用宏定义命令
// #define 宏名 字符串
#define PI 3.1415926
// #define 宏 (参数列表) 宏
#define A(x) x
- 使用宏定义时,需要注意:
1⃣️在简单宏定义的使用中,当替换文本所表示的字符串是一个表达式时,需要加上括号,否则容易引起误解和误用。(先替换再计算原则)
2⃣️类似的,在待参数的宏定义的使用中,也容易引起误解。
3⃣️在使用简单宏定义时,当字符串中不知一个符号时,加上括号表示出优先级;如果是带参数的宏定于,则要给宏体中的每个参数加上括号,最后整个宏体上再加上一个括号。
- do...while(0)的妙用
- 条件编译
#ifdef 标识符
程序段1
#else
程序段2
#endif
#if 表达式
程序段1
#else
程序段2
#endif
- extern "C" 块的应用
2、面向对象的C++
结构化编程,就是按照计算机的思维写出的代码,以功能为目标来构造应用系统,无法解决重用、维护、扩展的问题。
面向对象编程的主要思想是把构成文艺的各个事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述一个事物在解决问题中经历的步骤和行为。对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。
面向对象:封装、继承和多态。
2.1 类与对象
- 类与对象的概念
- 类,是创建对象的模板,一个类可以创建多个相同的对象;对象,是类的实例,是按照类的规则创建的。
- 属性是一个变量,用来表示一个对象的特征;方法是一个函数,用来表示对象的操作。对象的属性和方法统称为对象的成员(成员变量和成员函数)。
- 类是抽象的,不占用存储空间的;而对象是具体的,占用存储空间的。
- 成员函数
- 成员函数中可以使用类中的任何成员,包括私有的和公用的。
- 成员函数可以在类外定义,也可以在类内定义。
- 在类外定义成员函数时,必须先在类中声明,然后再在类外定义。在类外定义成员函数时,需要写明
类名::函数名
。
- 类的封装性
- 构造函数
- 数据成员是不能在类中初始化的,所以出现构造函数。
- 如果用户自己没有定义构造函数,那么C++系统就会自动为其生成一个构造函数,只是这个构造函数的函数体是空的,什么也不做,也不会进行初始化。
- 带参数/带默认参数的构造函数
- 构造函数的重载
- 析构函数
- 静态数据成员
- 类的静态数据成员拥有一块单独的存储区,而不管创建了多少个该类的对象。如果只声明类而未定义对象,类的一般数据成员不占内存空间,只有在定义对象时才会为对象的数据成员分配空间。但是静态数据成员不属于某一个对象,所以在为对象所分配的空间中不包括静态数据成员所占的空间。
- 类的静态成员的声明通常会放在一个类的实现文件中。
- C++静态数据成员被类的所有对象所共享,包括该类的派生类的对象。派生类对象与基类对象共享基类的静态数据成员。如果改变静态数据成员的值,则在各个对象中这个数据成员的值同时都改变。(全局变量)
- 静态数据成员在程序编译时被分配空间,到程序结束时释放空间。、
- 静态成员函数
- 如果要在类外调用公用的静态成员函数,要用类名和域运算符
::
:Box::volumn()。 - 静态成员函数与非静态成员函数的根本区别是:非静态成员函数有this指针,二静态成员函数没有this指针。由此决定了静态成员函数不能访问本类中的非静态成员。
#include <iostream>
using namespace std;
class CStudent{
public:
// 定义带参数的构造函数
CStudent (int n, int s): num(n), score(s){}
void total();
// 静态成员函数
static double average();
private:
int num;
int score;
// 静态数据成员所有对象共享,声明静态数据成员
static int count;
static int sum;
};
// 定义静态数据称焉
int CStudent::count = 0;
int CStudent::sum = 0;
// 定义非静态成员函数
void CStudent::total()
{
// 非静态成员函数中可以使用静态数据成员、非静态数据成员
sum+=score;
count++;
}
// 定义静态成员函数
// 直接饮用静态数据成员,不用加类名
double CStudent::average()
{
return sum*1.0/count;
}
int main()
{
CStudent stu1(1, 100);
stu1.total();
CStudent stu2(2, 98);
stu2.total();
CStudent stu3(3, 99);
stu3.total();
// 调用类的静态成员函数
// 函数名前加上`类::函数名`
cout << CStudent::average() << endl;
}
- 对象的存储空间
- 对象占用的存储空间 = 非静态成员变量+编译器为了CPU计算做出的数据对齐处理 + 支持虚函数所产生的负担
- 空类实例占用1byte空间
- 只有成员变量的类的存储空间 = 所有成员变量占用存储空间
- 有成员变量和静态成员变量的类的存储空间:静态成员变量不占对象的内存空间的。
- 只有一个成员函数的类的存储空间: 成员函数不占对象的内存空间
- 类中构造函数、析构函数的空间占用情况:同上。
- 类中有虚的析构函数的空间计算:编译器为了支持虚函数,会产生额外的负担,这正是指向虚函数表的指针的大小。如果一个类中有一个或多个虚函数,没有成员变量,那么类相当于含有一个指向想虚函数表的指针,占8byte。
- 继承空类和多重继承空类存储空间计算:但一继承的空类空间是1Byte,多重继承的空类空间是1Byte,但是虚继承设计虚表(虚指针),所以是8Byte。
- 综上所述:每个对象索占用的存储空间只是该对象的非静态数据成员总和,其他都不占用存储空间,包括成员函数和静态数据成员。
- this 指针
保证不同对象的成员函数引用的数据成员属于该对象。隐式使用。 - 类模板
#include <iostream>
using namespace std;
// template <class T>
class Operation
{
public:
Operation(T a, T b): x(a), y(b){}
T add()
{
return x+y;
}
T subtract()
{
return x - y;
}
private:
T x, y;
}
int main
{
Operation <int> oper_int(1, 2);
cout << oper_int.add() << " " << oper_int.subtract() << endl;
Operation <double> oper_double(3.5, 1.2);
cout << oper_double.add() << " " << oper_double.subtract() << endl;
return 0;
}
- 构造函数和析构函数执行顺序
- 再一般情况下,调用析构函数的次序正好与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用;而最后被调用的构造函数,其对应的析构函数最先被调用(栈)。➡️先构造的后析构,后构造的先析构。
2.2 继承和派生
- 继承和派生的一般形式
⬇️默认
class 派生类名 : [public | private | protected] 基类名
{
派生类新增加的成员;
};
- 从上面派生类定义的一般形式可以看出:派生类里有两大部分内容:从基类继承而来的和在声明派生类事增加的部分。
- 派生类虽然是用来避免代码中重复出现多个类中大部分内容雷同的情况,但由于派生类接受了基类的全部内容,这样可能出现有些基类的成员,在派生类中使用不到的,这就会造成数据的冗余,尤其是多次派生后,会在许多派生类对象中存在大量无用的数据,不仅浪费大量的空间,而且在对象的简历、复制、赋值和参数的传递中,花费许多无谓的时间,从而降低效率。慎重选择。
- 派生类的访问属性
- 无论哪种继承方式,在派生类是不能访问基类的私有成员的,私有成员只能被本类的成员函数所访问,毕竟派生类与基类不是同一个类。为了避免在多级派生之后程序猿记不清哪些成员可以访问,哪些成员不能访问,所以在实际中,常用的是共用继承。
- 派生类的构造函数与析构函数(看不懂👀)
- 派生类的构造函数与析构函数的调用顺序
- 总的来说,构造函数的调用顺序是:1⃣️如果存在基类,那么先调用基类的构造函数,如果基类的构造函数中仍然存在基类,那么程序会继续进行向上查找是,知道找到它最早的基类进行初始化;2⃣️如果所调用的类中定义的时候存在对象被声明,那么在基类的构造函数调用完成以后,再调用对象的构造函数;3⃣️调用派生类的构造函数。
2.3 类的多态
- 多态的概念
在C++程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法);也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。(与函数重载的区别)
- 函数重载虽然函数名一样,但函数类型、形参个数或类型不同;多态是指基类和派生类中成员函数名相同、类型相同、形参完全相同,但是两个同名函数不在同一个类中,而是分别在基类和派生类中,属于同名覆盖。
#include <iostream>
#include <string>
using namespace std;
/* 声明基类Box */
class Box
{
public:
// 声明构造函数
Box(int h, int w, int l);
// 声明输出函数
virtual void display();
protected:
int height, width, length;
}
Box::Box(int h, int w, int l)
{
length = l;
width = w;
height = h;
}
void Box::display()
{
cout << "length: " << length << endl;
cout << "width: " << width << endl;
cout << "height: " << height << endl;
}
class FilledBox : public Box
{
public:
FilledBox(int, int, int, int, string);
virtual void display();
private:
int weight;
string fruit;
}
FilledBox::FilledBox(int h, int w, int l, int we, string f) : Box(h,w,l), weight(we), fruit(f){}
void FilledBox::display()
{
cout << "length: " << length << endl;
cout << "width: " << width << endl;
cout << "height: " << height << endl;
cout << "weight: " << weight << endl;
cout << "fruit: " << fruit << endl;
}
int main(int argc, char const *argv[])
{
char* str = "hello world!";
char* str1 = "abc";
str = str1;
// printf("%s\n", str);
Box box(1,2,3);
FilledBox fbox(2,3,4,5,"apple");
Box *pt = &box;
pt->display();
pt = &fbox;
pt->display();
return 0;
}
- 虚函数的使用
- 使用虚函数时需要注意两点:
1⃣️只能用virtual关键字声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。因为虚函数的作用是允许在派生类中对基类的虚函数重新定义。显然,它只能用于类的继承层次结构中。
2⃣️一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同参数(包括个数和类型)和函数返回值类型的同名函数*了。 - 根据什么考虑是否把一个成员函数声明为虚函数?
- 纯虚函数
virtual void func() = 0;
- 动物基类可以派生出老虎,孔雀等,但用动物本身生成对象没有什么意义。
- 在基类中将函数定义为纯虚函数,则编译器要求在派生类中必须予以覆盖以实现多态性。
- 如果类中含有纯虚函数,这个类不能生成对象。
- 析构函数
- 单例模式
2.4 本章小结
- 继承从一定程度上破坏了封装,因为子类继承父类,修改父类导致所有子类的改变,因此继承一定程度上又破坏了系统的可扩展性,所以继承需要慎用。
- 多态:接口的多种不同的实现方式即为多态。接口是对行为的抽象。封装是找到对象的变化并封装起来,但是封装之后怎么适应接下来的变化呢?这就是接口的作用,接口的主要目的是为不相关的类提供通用的处理服务,从而实现系统的可维护性、可扩展性。
- 综上:面向对象实现了人们追求的系统的可维护性、可扩展性、可重用性。
6、TCP协议
关于这部分,因为作者是网络研究方向,且网络知识相当零碎,所以不再此处做笔记。改为纸质笔记。谢谢!
后记:我一直觉得想要学习一门语言(无论是编程语言还是沟通语言),最重要的就是用起来,没有什么比这个更快了。