《C++程序设计》朝花夕拾
(以后再也不用破Markdown写东西了,直到它有一个统一的标准,不然太乱了--)
函数签名
int f (int a, int b)
↑ ↑ ↑ ↑
返回类型 函数名 形 式 参 数
其中,函数名+形式参数=
函数签名(function signature)
。引用变量
int a = ;
int &b = a;
a++;
cout << a << ' ' << b << endl; //2 2注意引用变量的声明方式,&写在等号左边是引用变量的声明,写在右边就是取址符。
变量默认初始化
- 非Static的Local变量,默认初始化为任意值 //Local的Static变量默认初始化为0
- Global或Static变量,默认初始化为0(指针为NULL,NULL也是0)
Null Terminator '\0'
char s1[] = {'a', 'b', 'c', 'd', 'e'};
char s2[] = {'a', 'b', 'c', 'd', 'e'};
char s3[] = {'a', 'b', 'c', 'd', 'e', '\0'};
char s4[] = {'a', 'b', 'c', 'd', 'e', '\0'};
char s5[] = "abcde";
char s6[] = "abcde"; //若char s6[5] = "abcde";则编译出错
cout << s1 << ' ' << strlen(s1) << endl; //abcde#' 7
cout << s2 << ' ' << strlen(s2) << endl; //abcde 5
cout << s3 << ' ' << strlen(s3) << endl; //abcde 5
cout << s4 << ' ' << strlen(s4) << endl; //abcde 5
cout << s5 << ' ' << strlen(s5) << endl; //abcde 5
cout << s6 << ' ' << strlen(s6) << endl; //abcde 5这里面有几个知识点:
- 比较s1和s3,之所以s1多输出一些奇怪字符,就是因为没有加终止符'\0'
- 比较s1和s2,数组若分配了大小,则C++会自动在字符串末尾添加'\0'(其实这一点很容易想到,因为分配了大小的数组,如果元素没有被填满,则剩余元素均会被初始化为0,而0就是'\0')
- 比较s1和s5,s5_直接写成字符串的形式,C++会自动在字符串末尾添加'\0'_
- 比较s3和s6,s3中发现终止符'\0'并不影响strlen的大小;s6中却发现分配大小时需要将终止符考虑上(好奇怪的一点,也就是说,分配大小需要考虑'\0',而strlen会忽略'\0')
- 比较s1和s2,s3和s4,s5和s6,发现数组初始化的大小并不影响strlen。所以strlen的实现方法应该是,遍历数组直到发现'\0',char s = {'a', '\0', '\a'}的strlen其实是1
NULL
源码实现应该是
#define NULL 0
,所以null和Null都是错的,只有NULL才对。*、++、+的运算级别
*p++
你觉得应该是先算p++呢还是*p呢*p+1
你觉得应该先算p+1呢,还是*p呢*p++ = *q++;是将*q的值赋给*p,然后p++,q++
int *i, j
你觉得这样声明的话,j是一个int,还是一个int的指针?
static
static变量的初始化——类外初始化
class A
{
static int x; //不能直接在声明的时候初始化
};
int A::x = ; //类外初始化必须加上int和A::只有static const变量才能直接在类内声明时初始化
class A
{
static const int x = ;
};static函数的调用——通过类名调用
class A
{
public:
static void f(){}
};
A a;
a.f(); //correct
A::f(); //correct
A.f(); //wrong
friend
注:这里的子类指的是子类的成员函数,而不是子类对象(子类对象在类外)public继承、protected继承和private继承
先看一个表
再看下面的代码
#include <iostream>
using namespace std; class A
{
public:
void f1()
{
cout<<"f1()"<<endl;
} protected:
void f2()
{
cout<<"f2()"<<endl;
} private:
void f3()
{
cout<<"f3()"<<endl;
}
}; class B: public A
{
public: // 子类的代码范围内访问父类
void g1(){f1();}
void g2(){f2();}
//void g3(){f3();} error }; int main()
{
B b; //子类对象访问父类成员
b.f1();
//b.f2(); error
//b.f3(); error
b.g1();
b.g2();
//b.g3(); error
}仔细看上面的表格和代码之后再继续往下看,这里我一直有个误解的地方,就是子类不是能够访问父类的protected成员吗,为什么子类对象访问父类的protected成员时会出错。
原来,我们所谓的子类能够访问父类的protected成员,指的是在子类的类内去访问,而子类对象其实已经属于类外了。所以把子类对象当成是普通的外界访问即可。
接着,上面的代码中,子类是通过public方式继承,如果换成protected和private继承会发生什么事呢?继续看。
三种访问权限
- public:可以被任意访问
- protected:只允许子类及本类的成员函数访问
- private:只允许本类的成员函数访问
三种继承方式
- public 继承
- protect 继承
- private 继承
见下表
可以看到,子类的成员属性其实是基类属性和继承方式中,取较严格的一个,当然基类的private成员子类是无权访问的。
这里说的不是很清楚,补充一下,“子类成员属性”属性指的是子类继承基类的成员的属性,自己本身特有的那些成员该是什么属性就什么属性。所以如果换成private继承的话,b.f1()就会出错,b.g1()还是对的。这个时候,回过头去看上面的表格和代码,就一目了然了吧。
父类的构造函数只能在子类的构造函数中调用
class A
{
public:
int x,y;
A(int x1, int y1):x(x1),y(y1){}
};
class B: public A
{
public:
int z;
B(int x1, int y1, int z1):A(x1,y1),z(z1){}
};顺便看一下如何使用
初始化列表
来初始化参数。简单谈谈泛型编程
举个例子,A是父类,B是子类
void f(A a){}
B b;
f(b);You can pass a derived class object to the base class parameter type in the function.
This is a kind of 'Generic Programming'
另外,STL也是一种泛型编程(利用模版T来抽象数据类型)。所谓“泛型编程”,精华在于,
把数据类型作为一种参数传递进来
,而不关心是哪种具体的数据类型。子类调用被重定义的父类函数
class A
{
public:
void f(){cout << "A" << endl;}
};
class B: public A
{
public:
void f(){cout << "B" << endl;}
};
B b;
b.f(); //B
b.A::f(); //A可以看到,由于B中的f()重定义了A中的f(),所以默认调用了B中的f(),若想重新调用A中的f(),则需要加上A::。
注意一,如果是A* a = &b;
b->f(); //A则由于A中的f()没有声明为虚函数,所以默认调用A中的f()。
注意二,如果是
void g(A a){a.f()}
g(b); //A则即使A中的f()声明为虚函数,也仍然输出A,因为参数传递时已经进行了类型转换。所以实现多态时,参数必须是引用 或指针啦~
动态绑定(多态)
动态绑定即在程序运行的时候才确定调用哪个函数,跟多态有关。
动态绑定需要满足两个条件:- 父类有虚函数
实现多态的函数的参数必须包含对象的地址(引用 或指针)
举个例子,A是父类,B是子类,实现多态无非有以下两种方式。class A
{
public:
virtual void f(){cout << "A" << endl;}
};
class B: public A
{
public:
void f(){cout << "B" << endl;}
};
void g1(A &a) {a.f();}
void g2(A *a) {a->f();}
B b;
g1(b); //方法1通过引用,输出B
g2(&b); //方法2通过指针,输出B
A *a = &b;
a->f(); //同方法2通过指针,输出B
抽象类
假如有父类是多边形,子类是圆形和矩形,那么计算面积的函数应该声明在子类里呢还是父类里呢?由于面积是多边形的共性,所以应放在父类里,但是面积的计算方式又跟子类的类型息息相关,即只有确定了子类类型,计算面积的函数才能真正实现。
所以这个时候就需要纯虚函数
,将计算面积的函数声明为父类的纯虚函数,纯虚函数又叫抽象函数
,不提供实现,一个包含纯虚函数的类叫做抽象类
,抽象类无法创建对象。
纯虚函数的声明方式:virtual void getArea()=;
Dynamic cast
假如有父类是多边形,子类是圆形和矩形,圆形的类中有getRadius()这个函数,而矩形的类中没有,假如一个函数的参数类型是父类,这个函数要判断该多边形是否是圆形,如果是则输出半径(getRadius())。怎么办?
class A
{
public:
virtual void f(){cout << "A" << endl;}
};
class B: public A
{
public:
void f(){cout << "B" << endl;}
void f2(){cout << "BB" << endl;} //B特有的函数
};
class C: public A
{
public:
void f(){cout << "C" << endl;}
}; void g1(A &a)
{
A *a2 = &a; //先转换为指针,再进行指针间的转换
B *b = dynamic_cast<B*>(a2); //a2进行了dynamic cast
if (b != NULL)b->f2(); //如果不是B类型,则b为NULL
}
void g2(A *a)
{
B *b = dynamic_cast<B*>(a); //a进行了dynamic cast
if (b != NULL)b->f2(); //如果不是B类型,则b为NULL
}
B b;
C c;
g1(b); //BB
g1(c); //
g2(&b); //BB
g2(&c); //dynamic cast一般是downcasting(父类到子类),而upcasting(子类到父类)的转换比较简单,直接(隐式)转换即可。
A *a = new A();
B *b = new B();
a = b; //b进行了upcasting,将结果赋给a,b本身没有变运算符重载
类内成员函数:
bool operator <(T &t)
T operator +(T &t)
T operator +=(T &t)
T& operator[](int &index) //T& 保证返回的值可成为左值
T operator +()
T operator ++() //++var
T operator ++(int dummy) //var++(dummy)
friend ostream & operator <<(ostream &stream, T &t)
operator double()
T& operator =(T &t)非类内成员函数:
bool operator <(T &t1, T &t2)
T operator +(T &t1, T &t2)
……参数为什么要引用?
节省传值方式中实参的拷贝方面的开销。为什么要返回引用类型?
首先先明白什么是左值(L-value)和右值(R-value)。
比如a = b,a是左值,b是右值。
赋值符号左边的值是左值,它是一个地址(很奇怪吧哈哈);而赋值符号右边的值是右值,它是一个数据。
左值是代表一个内存地址值,并且通过这个内存地址,就可以对内存进行读并且写(主要是能写)操作;这也就是为什么左值可以被赋值的原因了。
右值指的是引用了一个存储在某个内存地址里的数据。
左值可以被改变,而右值不可以变。所以左值可以作为右值,而右值不一定能成为左值。
比如a = 2, b = a中a是左值,也可以成为右值,而2是右值,不能成为左值。
抽象点说,左值应该是一个类似变量的东西,而不是某个常量(个人理解)。
比如T& operator =(T &t),如果返回的是T而不是T&
那么a=b=c这样的式子会出错,为什么,因为b=c返回的是一个常量,而参数要求是T &t,此时可以将T &t改为T t或const T &t。
由于a=b=c的计算顺序是a=(b=c),所以刚好不会出错。
但是会有一个问题,就是(a=b)=c这样的式子在现实中也是有意义的,但编译却通过不了,因为a=b返回的是一个常量,常量不能被赋值(不能成为左值)。
所以最好还是返回一个引用类型。
比如T& operator[](int &index),如果返回的是T而不是T&
那么s[1]=2这样的式子就会出错,因为s[1]返回的是一个常量,不能成为左值(常量是不能被赋值的)。
比如friend ostream & operator <<(ostream &stream, T &t),如果返回的是ostream而不是ostream &
cout << a <<" ";这句就会出错,因为cout << a返回一个ostream类型的值,而这个值并没有一个类似变量的东西去承载它,只是一个值,不能成为左值。(类似a = 1是对的而1 = 1是错误的)什么时候参数需要加const
当你不希望该参数在该函数中被改变的时候可以加,但不是必须的。T operator ++() //++var
T operator ++(int dummy) //var++(dummy)
怎么记?第一个没有参数,类似正号的重载T operator +()
而第二个有一个参数dummy(虚拟的),类似加号的重载T operator +(T &t)
Template
- 模板的默认值Template< typename T=int>只适用于类模板,不能用于函数模板
然后这样使用:Stack<> stack,而不是Stack stack - 模板可以这样声明,template< typename T, int size>
然后这样使用:Stack< int, 500> stack
- 模板的默认值Template< typename T=int>只适用于类模板,不能用于函数模板
复杂度的计算
以前一直以为快速排序的复杂度是这样计算的,每一层递归需要对每个元素进行划分(左或右),所以每一层都是O(n),一共有logn层,所以是O(nlogn)。但是这样是错的,正确的应该从递推公式出发:
T(n) = T(n/2)+T(n/2)+n
推出T(n) = nlogn+n = O(nlogn)假如一个函数是
void f(int n)
{
if (n == ) return;
f(n/);
f(n/);
}则按照我以前的想法,一共有logn层,每一层复杂度是O(1),所以O(logn).但是正确的应该是:
T(n) = T(n/2)+T(n/2)+C
推出T(n) = O(n)External Sort(外部排序)
当内存太小不足以读取全部数据的时候,普通的排序算法不能用了,要用到外部排序。
所谓的外部排序,就是将数据分成N批依次读进内存,对每一批,用内部排序算法(普通排序算法)排序,将结果写进一个新的文件中,这样就有N个排序好的文件。然后对这些文件进行两两归并排序,直到剩下一个文件。STL
STL包含三个部分:
- 容器
- 顺序容器(vector, list, deque)
- 关联容器(set, map, multiset, multimap)
- 容器适配器(stack, queue, priority_queue)
- 迭代器
- 算法
- 容器
Iterator
list< int>::const_iterator it; //指向的内容不可改变
const list< int>::iterator it; //迭代器自身不可改变
reverse_iterator //rbegin是最后一个,rend是第一个之前的位置,rbegin通过递增
直到rend,懂?
Pointer是一种特殊的Iterator
Iterator是泛化的PointerSTL Algorithm
int a[] = {,,,,};
vector<int> v();
copy(a, a+, v.begin());
ostream_iterator<int> out(cout, " ");
copy(v.begin(), v.end(), out);copy(sourse.begin(), sourse.end(), target.begin()) fill(source.begin(), sourse.end(), value)
fill_n(source.begin(), n, value)generate(source.begin(), sourse.end(), f(int))
generate_n(source.begin(), n, f(int)) //remove后原容器的size不变,空位由原来的值补全
remove(source.begin(), sourse.end(), value)
remove_if(source.begin(), sourse.end(), f(int))
//source不变,将remove之后的结果依次赋给target
remove_copy(source.begin(), sourse.end(), target.begin(), value)
remove_copy_if(source.begin(), sourse.end(), target.begin(), f(int)) replace(source.begin(), sourse.end(), oldValue, newValue)
replace_if(source.begin(), sourse.end(), f(int), newValue)
replace_copy(source.begin(), sourse.end(), target.begin(), oldValue, newValue)
replace_copy_if(source.begin(), sourse.end(), target.begin(), f(int), newValue) find(source.begin(), sourse.end(), value)
find_if(source.begin(), sourse.end(), f(int))
//在source中找key(子串匹配),返回最后一个匹配的起始位置
find_end(source.begin(), sourse.end(), key.begin(), key.end())
find_end(source.begin(), sourse.end(), key.begin(), key.end()), f(int1,int2))
//key中有一个元素符合条件就行,而不是整个子串
find_first_of(source.begin(), sourse.end(), key.begin(), key.end())
find_first_of(source.begin(), sourse.end(), key.begin(), key.end(), f(int1,int2)) //与find_end相反,返回第一匹配子串的起始位置
search(source.begin(), source.end(), key.begin(), key.end())
search(source.begin(), source.end(), key.begin(), key.end(), f(int1,int2))
//查找连续相同字符,返回第一个匹配的位置
search_n(search(source.begin(), source.end(), n, value)
search_n(search(source.begin(), source.end(), n, f(int)) //这个不多说了,经常用,经常用到的cmp(int)是greater()
sort(source.begin(), source.end())
sort(source.begin(), source.end(), cmp(int1, int2))
binary_search(source.begin(), source.end(), value)
binary_search(source.begin(), source.end(), value, cmp(int1, int2)) //找出相邻的两个相等的元素
adjacent_find(source.begin(), source.end())
adjacent_find(source.begin(), source.end(), f(int1, int2)) //merge将2个排序好的序列存到其他地方,而inplace_merge是存到自身
merge(s1.begin(), s1.end(), s2.begin(), s2.end(), t.begin())
merge(s1.begin(), s1.end(), s2.begin(), s2.end(), t.begin(), cmp(int1, int2))
inplace_merge(source.begin(), source.middle(), source.end())
inplace_merge(source.begin(), source.middle(), source.end(), cmp(int1, int2)) //reverse_copy source不变
reverse(sourse.begin(), source.end())
reverse_copy(sourse.begin(), source.end(), target.begin()) rotate(sourse.begin(), source.newBegin(), source.end())
rotate_copy(sourse.begin(), source.newBegin(), source.end(), target.begin()) //swap_range是将[begin,end-1]和[begin2,...]交换,个数一样
swap(T1, T2)
iter_swap(it1, it2)
swap_ranges(source.begin(), source.end(), source.begin2()) count(sourse.begin(), source.end(), value)
count_if(sourse.begin(), source.end(), f(int)) //返回的是iterator
max_element(sourse.begin(), source.end())
min_element(sourse.begin(), source.end()) //随机打乱[begin, end-1]
random_shuffle(sourse.begin(), source.end()) for_each(sourse.begin(), source.end(), f(int))
transform(sourse.begin(), source.end(), target.begin(), f(int)) //交并差,对称差(A并B减掉A交B)
includes(s1.begin(), s1.end(), s2.begin(), s2.end())
set_union(s1.begin(), s1.end(), s2.begin(), s2.end(), target.begin())
set_difference(s1.begin(), s1.end(), s2.begin(), s2.end(), target.begin())
set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(), target.begin())
set_symmetric_difference(s1.begin(), s1.end(), s2.begin(), s2.end(), target.begin()) //求容器元素的和加上value的和
accumulate(source.begin(), source.end(), value)
accumulate(source.begin(), source.end(), value, minus()) //t[0]=s[0],t[i]=s[i]-s[i-1];
adjacent_difference(source.begin(), sourse.end(), target.begin())
adjacent_difference(source.begin(), sourse.end(), target.begin(), multiplies()) //value+s1[0]*s2[0]+s1[1]*s2[1]+……
inner_product(s1.begin(), s1.end(), s2.begin(), value)
//value*(s1[0]-s2[0])(s1[1]-s2[1])……
inner_product(s1.begin(), s1.end(), s2.begin(), value, minus(), plus()) //target[i]表示source前i项的和
partial_sum(source.begin(), source.end(), target.begin())
partial_sum(source.begin(), source.end(), target.begin(), minus())
《C++程序设计》朝花夕拾的更多相关文章
- 20145213《Java程序设计》第三周学习总结
20145213<Java程序设计>第三周学习总结 教材学习内容总结 正所谓距离产生美,上周我还倾心于Java表面的基础语法.其简单的流程结构,屈指可数的基本类型分类,早已烂熟于心的运算符 ...
- java基础梳理--朝花夕拾(三)
1.了解面向对象的编程思想以及面向对象的特性: 对象: EveryThing is Object: 万物皆是对象,在程序中,我们可以将类.接口.方法.属性等都认为是对象: 面向对象: 是一种程序设计方 ...
- java基础梳理--朝花夕拾(一)
简介: Java是一种撰写跨平台应用软件的面向对象语言,1995年由Sun Microsystems公司推出. 2009年04月20日,甲骨文74亿美元收购Sun,取得java的版权. 2011年7月 ...
- HTML5 程序设计 - 使用HTML5 Canvas API
请你跟着本篇示例代码实现每个示例,30分钟后,你会高喊:“HTML5 Canvas?!在哥面前,那都不是事儿!” 呵呵.不要被滚动条吓到,很多都是代码和图片.我没有分开写,不过上面给大家提供了目录,方 ...
- 解析大型.NET ERP系统 单据标准(新增,修改,删除,复制,打印)功能程序设计
ERP系统的单据具备标准的功能,这里的单据可翻译为Bill,Document,Entry,具备相似的工具条操作界面.通过设计可复用的基类,子类只需要继承基类窗体即可完成单据功能的程序设计.先看标准的销 ...
- java基础学习03(java基础程序设计)
java基础程序设计 一.完成的目标 1. 掌握java中的数据类型划分 2. 8种基本数据类型的使用及数据类型转换 3. 位运算.运算符.表达式 4. 判断.循环语句的使用 5. break和con ...
- CWMP开源代码研究5——CWMP程序设计思想
声明:本文涉及的开源程序代码学习和研究,严禁用于商业目的. 如有任何问题,欢迎和我交流.(企鹅号:408797506) 本文介绍自己用过的ACS,其中包括开源版(提供下载包)和商业版(仅提供安装包下载 ...
- 《JavaScript高级程序设计(第3版)》笔记-序
很少看书,不喜欢看书,主要是上学时总坐不住,没有多大定性,一本书可以两天看完,随便翻翻,也可以丢在角落里几个月不去动一下. 上次碰到了<JavaScript高级程序设计(第3版)>感觉真的 ...
- 《JavaScript高级程序设计(第3版)》阅读总结记录第一章之JavaScript简介
前言: 为什么会想到把<JavaScript 高级程序设计(第 3 版)>总结记录呢,之前写过一篇博客,研究的轮播效果,后来又去看了<JavaScript 高级程序设计(第3版)&g ...
随机推荐
- Tomcat性能调整完整教程
Tomcat性能调整完整教程 发表于:2007-07-13来源:作者:点击数:526 标签: 一. 引言 性能测试与分析是软件 开发 过程中介于架构和调整的一个广泛并比较不容易理解的领域,更是一项较为 ...
- <译>Zookeeper官方文档
apache原文地址:http://zookeeper.apache.org/doc/trunk/zookeeperOver.html ZooKeeper ZooKeeper: A Distribut ...
- 禁止chrome自动更新
删除C:\Program Files (x86)\Google文件夹下面的updata文件夹
- 捕获海康威视IPCamera图像,转成OpenCV能够处理的图像(一)
海康威视IPCamera图像捕获 捕获海康威视IPCamera图像,转成OpenCV能够处理的IplImage图像(一) 捕获海康威视IPCamera图像.转成OpenCV能够处理的IplImage图 ...
- js+css简单效果(幕布,跑马灯)
2.js普通的盒子,css的优先级 css的优先级 !important >>>>> style 行内样式 >>>>> #id选择器 # ...
- 使用jq Deferred防止代码被回调函数分解分解的支离破碎
//移动人物 function moveInterval(stopPosotion){ var dtd = $.Deferred(); // 生成Deferred对象 var yidong= wind ...
- web开发中常见的安全漏洞及避免方法
1.安全攻击 1.SQL.HTML.JS.OS命令注入 2.XSS跨站脚本攻击,利用站内信任的用户,在web页面插入恶意script代码 3.CSRF跨站请求伪造,通过伪装来自信任用户的请求来利用受信 ...
- $("#SpecialAptitude").on("change",function(){CheckType($(this))})$("#SpecialAptitude").on("change",CheckType($(this)))
$("#SpecialAptitude").on("change",function(){CheckType($(this))})$("#Specia ...
- python-嵌套循环(Nested loop)-久久乘法表
嵌套-久久乘法 for i in range(1,10): for j in range(1,10): print('{} × {} = {}'.format(i,j,i*j))最外层的循环依次将数值 ...
- Easyui 二级菜单
<div class="fitem"> <label>所在城市:</label> <input id="cityId" ...