C++疑难点
this指针
调用成员函数时,成员函数通过一个名为 this 的隐式参数来访问调用它的那个对象,用请求该函数的对象地址初始化 this ,this 的指向总是自己这个对象,所以 this 是一个常量指针
Box* get_address() //得到this的地址
{
return this;
}
Box* get_address() //得到this的地址
{
Box box;
this = &box; // error: lvalue required as left operand of assignment
return this;
}
友元函数和运算符重载
#include <iostream>
#include <cmath>
using namespace std;
class MyInteger
{
public:
MyInteger() { this->num_ = 0; }
MyInteger(int num) : num_(num) {}
MyInteger operator+(const MyInteger &other)
{
MyInteger temp;
temp.num_ = this->num_ + other.num_;
// this->num_ += m.num_; // 这违反了加法运算符的语义,加法运算通常是创建一个新的对象来保存结果,而不是改变原始对象。
return temp;
}
MyInteger operator+(const int &other) // 运算符重载
{
MyInteger temp;
temp.num_ = this->num_ + other;
return temp;
}
MyInteger operator+=(const MyInteger &other)
{
MyInteger temp;
temp.num_ = this->num_ + other.num_;
return temp;
}
MyInteger operator+=(const int &other)
{
MyInteger temp;
temp.num_ = this->num_ + other;
return temp;
}
MyInteger &operator++()
{
++num_;
return *this;
}
MyInteger operator++(int)
{
MyInteger temp(*this);
num_++;
return temp;
}
MyInteger &operator=(const MyInteger &myInteger) // 注意深浅拷贝问题
{
num_ = myInteger.num_;
}
friend ostream &operator<<(ostream &out, const MyInteger &myInteger); // 重载左移运算符 没加 const ,cout << m++;报错
private:
int num_;
};
ostream &operator<<(ostream &out, const MyInteger &myInteger)
{
out << myInteger.num_;
return out;
}
void test01()
{
MyInteger m;
MyInteger m1(10);
m1 = m + m1;
cout << m << endl;
cout << m1 << endl;
}
void test02()
{
MyInteger m(1);
MyInteger m1(100);
m = m1 + m;
m1 += 10;
cout << m << " " << m1 << endl;
}
void test03()
{
MyInteger m(2);
MyInteger m1(8);
// cout << ++(++m) << " " << ++m1 << endl;
// cout << m << " ";
cout << m++;
cout << m;
}
int main()
{
// test01();
// test02();
test03();
// int a=1;
// int b=2;
// a=a+b;
// cout<<a<<" "<<b<<endl;
int c = 1;
cout << endl;
cout << c++ << endl;
cout << c << endl;
return 0;
}
不能重载的运算符
.,:: ,?:,sizeof,typeid,.* 这几个运算符不能被重载
C++编译器至少给一个类提供四个函数
1、构造函数
2、析构函数
3、拷贝构造函数
4、operator= 函数
多态的实现
多态实现的前提三个条件
1、必须是共有继承
2、通过基类指针指向派生类,并且访问派生类重写的方法。
3、基类中的被重写的方法是虚函数
这种技术让父类指针有多种形态,是一种泛型技术,直到运行时才决定执行哪个版本的函数。所谓泛型技术,就是使用不变的代码来实现可变的算法。多态中没有重写的函数是没有意义的。
水能载舟,亦能覆舟。多态也涉及了安全性的问题,
首先是无法访问子类中自己的虚函数,如Base * base=new Derived(); base->f1();
编译时不会通过的(其中f1()是子类自己的虚函数,父类没有);
然后是如果父类中虚函数是private或者protected,这些函数依旧会存在于虚函数表中,可以通过多态的方式来访问。
#include <iostream>
using namespace std;
class Father
{
public:
virtual void f(){
cout<<"hello Father!"<<endl;
}
};
class Son : public Father
{
public:
void f(){
cout << "hello Son!"<<endl;
}
};
int main() {
Son s;
Father *p = &s;
p->f();
Father f;
p=&f;
p->f();
return 0;
}
菱形继承的问题
继承关系画成图像一个菱形,所以就叫做菱形继承,采用虚继承来解决二义性问题。
虚析构
总的来说是为了避免内存泄漏,当子类中有指针成员变量时才会使用到。也就是说,虚析构函数使得删除指向子类的父类指针时,不仅可以调用父类的的析构函数,也会调用子类的析构函数,这样就可以释放子类指针成员变量在堆中的内存,达到防止内存泄漏的目的。
#include <bits/stdc++.h>
using namespace std;
class CA
{
public:
CA() { cout << "CA" << endl; }
virtual void f1()
{
cout << "CA::f1( )" << endl;
// f2();
}
void f2()
{
cout << "CA::f2( )" << endl;
}
virtual ~CA()
{
cout << "~CA" << endl;
}
};
class CB : public CA
{
public:
CB() { cout << "CB" << endl; }
virtual void f1()
{
cout << "CB::f1( )" << endl;
}
void f2()
{
cout << "CB::f2( )" << endl;
}
virtual ~CB()
{
cout << "~CB" << endl;
}
};
class CC : public CB
{
public:
CC() { cout << "CC" << endl; }
void f1()
{
cout<<"CC:f1()"<<endl;
}
void f2()
{
cout << "CC:f2()" << endl;
}
virtual ~CC()
{
cout << "~CC" << endl;
}
};
int main()
{
CA *pA = new CC();
pA->f1();
delete pA;
CA *pA1 = new CB();
pA1->f1();
delete pA;
return 0;
}//注意看构造和析构的顺序,正好是相反的
g++和gcc的区别
面试的时候,问到了gcc和g++的区别,没答上来:
首先说明:gcc 和 GCC 是两个不同的东西
GCC:GNU Compiler Collection(GNU 编译器集合),它可以编译C、C++、JAV、Fortran、Pascal、Object-C、Ada等语言。
gcc是GCC中的GNU C Compiler(C 编译器)
g++是GCC中的GNU C++ Compiler(C++编译器)
一个有趣的事实就是,就本质而言,gcc和g++并不是编译器,也不是编译器的集合,它们只是一种驱动器,根据参数中要编译的文件的类型,调用对应的GUN编译器而已,比如,用gcc编译一个c文件的话,会有以下几个步骤:
Step1:Call a preprocessor, like cpp.
Step2:Call an actual compiler, like cc or cc1.
Step3:Call an assembler, like as.
Step4:Call a linker, like ld
由于编译器是可以更换的,所以gcc不仅仅可以编译C文件
所以,更准确的说法是:gcc调用了C compiler,而g++调用了C++ compiler
gcc和g++的主要区别:
-
对于 .c和.cpp文件,gcc分别当做c和cpp文件编译(c和cpp的语法强度是不一样的)
-
对于 .c和.cpp文件,g++则统一当做cpp文件编译
-
使用g++编译文件时,g++会自动链接标准库STL,而gcc不会自动链接STL
-
gcc在编译C文件时,可使用的预定义宏是比较少的
-
gcc在编译cpp文件时/g++在编译c文件和cpp文件时(这时候gcc和g++调用的都是cpp文件的编译器)
关于内联函数
一般是加快程序执行速度,可能减小可执行文件大小,可能增加可执行文件大小。
速度快:当函数体较短时,内敛函数会像宏一样展开,所以执行速度比一般函数要快。但是如果函数体过大,一般的编译器会放弃内联方式,意思就是你使用内联函数,只不过是向编译器提出了一个申请,编译器可以拒绝,这个函数又会像普通函数一样,执行效率也和普通函数一样。
减小可执行文件大小:内联函数适度
增加可执行文件大小:内联函数过多
仿函数
在c++中仿函数(Functor)是一个类或者结构体,重载了函数调用运算符()
,它的主要作用是提供一种更加灵活的函数对象,它可以包含状态信息,并且可以被传递给算法或者函数,从而实现定制的行为。比如实现排序准则、查找准则、谓词(就是一个bool返回值的函数)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void Print(int val){cout<<val<<" ";}
class Print1
{
public:
void operator()(int val){cout<<val<<" ";}
};
void test01()
{
vector<int> v;
v.push_back(6);
v.push_back(66);
for_each(v.begin(), v.end(), Print1());
cout<<endl<<"--------------"<<endl;
for_each(v.begin(), v.end(), Print);
}
int main()
{
test01();
return 0;
}
pointer-like-class的作用
pointer-like-class
是指类似指针的类,这种类一般用来模拟指针的行为,但是与裸指针相比具有更多的功能和安全性,如智能指针(Smart Pointers)、迭代器(Iterators)。Smart Pointers有std::unique_ptr
和std::shared_ptr
、std::weak_ptr
。
一:结构化程序设计方法
位运算及其运用
判断奇数还是偶数
int a=11;
int b=1;
if(b&a){
cout << "is odd" <<endl;
}else{
cout<< "is even" <<endl;
}
取出指定的位
int a=0b11101101;
int b=0b1111;
cout << bitset<sizeof(char)*8>(a&b)<<endl;
判断是否为2的整数幂(0b10 0000 & 0b01 1111),结果是0,说明是2的整数幂。
int x = 64;
int y = x - 1;
cout << ((x & y) ? "no" : "yes") << endl;
指针数组与指向数组的指针的概念与使用
a是一个指向含有三个int型数据数组的指针(如有int p[3]; a指向p是可以的 )
a1是一个函数三个int*型数据的数组(如a1可以初始化为{&b, &c, &d});
#include <iostream>
#include <typeinfo>
using namespace std;
#define sz(type) cout<<sizeof(type)<<endl;
int main(){
int b=10, c=1, d=2;
int (*a)[3];
int* a1[3];
cout<<typeid(a).name()<<endl;
sz(a);
sz(a1);
sz(a1[0]);
sz(*a)
sz(a[0][0])
return 0;
}
引用的分类与使用
1、初始化,定义引用时需要加&,
int a=1;
int &b=a;//相当于给a取了一个别名
2、作为函数参数传递,c++在函数参数中传递数组时,直接变成了指针,因为如果将数组传递过去需要将值一个一个拷贝过去,增加了函数调用的开销,所以在 C++ 中,我们有了一种比指针更加便捷的传递聚合类型数据的方式,那就是引用(Reference),通过这种方式传过去减少了生成副本的消耗。
void swap(int &a, int &b){
int temp=a;
a=b;
b=temp;
}
3、作为函数返回值来传递,例如重载左移运算符,需要输出多个内容就需要返回引用。
#include <iostream>
#include <typeinfo>
using namespace std;
#define sz(type) cout << sizeof(type) << endl;
int function1(int &aa) // 以返回值的方法返回函数值
{
return aa;
}
int &function2(int &aa) // 以引用方式返回函数值
{
return aa;
}
int main()
{
int a = 10;
// 第一种情况,系统生成要返回值的副本(即临时变量)
int b = function1(a); // function1()的返回值先储存在一个a的副本中,
// 然后再把副本赋值给b
// 第二种情况,报错
// function1(a) = 20;// function1()的返回值为临时变量,不能赋值(即不能为左值)
// 第三种情况,系统不会生成返回值的副本
function2(a) = 20; // OK 此时a的值变成了20
cout<<a<<endl;
}
重载左移运算符,返回引用才会连续输出两个值;
#include <iostream>
using namespace std;
class Point
{
public:
int x_, y_;
Point(int x, int y) : x_(x), y_(y) {}
friend ostream &operator<<(ostream &os, const Point &p)
{
os << p.x_ << " " << p.y_ << endl;
return os;
}
};
int main()
{
Point p(1, 2);
cout<<p<<p;
return 0;
}
变量的作用域和生命周期
c++的内存主要分为一下几个部分:栈区、堆区、全局区(静态区)、文字常量区、程序代码区。
全局变量
存储在静态内存分配区,整个程序的生命周期都可以使用,其他文件使用关键字extern也可以使用
局部变量
存储在栈区,与函数共存亡
全局静态变量
与全局变量类似,也是存储在静态内存分配区,生命周期与整个程序同在,不过不能再其他文件使用。
局部静态变量
也是存储在静态内存分配区,调用函数后便一直存在,只不过只能在函数内可见。
类型别名与类型推断
1、别名:typedef、using
2、推断:auto、decltype
STL顺序容器
vector:视作可变大小的数组,可随机访问,在非尾部的位置插入比较慢
deque:双端队列,支持随机访问,在头尾位置插入和删除比较快
list:双向链表,支持双向顺序访问(rbegin()、rend()),在 list 任意位置插入和删除都比较快
forward_list:单向链表,只能单向顺序访问,在 forward_list 的任意位置插入和删除都比较快
array:固定大小的数组,支持随机访问,不能添加或者删除元素
string:与 vector 类似,专门保存字符串,随机访问快,在尾部插入和删除快
STL关联容器
map:键值对,一对一,基于红黑树,对关键字进行排序
set:只保存关键字,不重复
multimap:关键字可以重复的 map ,即一对多,如统计数学课的学生成绩
multiset:保存可以重复的关键字
unordered_map:键值对,一对一,基于哈希值,不对键值对进行排序
unordered_set:只保存关键字,不排序
unordered_multimap:键值对,一对多,基于哈希值
unordered_multiset:保存可以重复的关键字,不排序
STL容器适配器
包括:stack、queue、priority_queue
stack 和 queue 基于deque实现, priority_queue 基于 vector 实现
流对象
包括输入流对象(ostream,如std::cin,从键盘读取数据),输出流对象(istream,如std::cout,向屏幕写入数据)
还有文件输入流对象(ifstream,使用 std::ifstream 来创建对象,将数据从文件中读取出来),文件输出流对象(ofstream,使用 std::ofstream 来创建对象,将数据写入文件)
#include <iostream>
#include <fstream>
using namespace std;
void test01()
{
ofstream file;
file.open("text.txt", ios::out);
file << "xinm" << endl;
file << "namji" << endl;
file.close();
}
void test02()
{
ifstream ifile;
ifile.open("text.txt", ios::in);
if(ifile.is_open()){
char buf[1024];
while(ifile >> buf)
{
cout<< buf<<endl;
}
}
ifile.close();
}
int main()
{
test02();
return 0;
}
使用二进制流读写文件
#include <iostream>
#include <fstream>
using namespace std;
class Person
{
public:
// Person(char name[], int age):name_(name), age_(age){}
char name_[20];
int age_;
};
void test01()
{
ofstream file;
file.open("text.txt", ios::out | ios::binary);
Person p = {"hhhhh", 18};
file.write((const char *)&p, sizeof(p));
file.close();
}
void test02()
{
ifstream ifile;
ifile.open("text.txt", ios::in | ios::binary);
if (ifile.is_open())
{
// 1
// char buf[1024];
// while(ifile >> buf)
// {
// cout<< buf<<endl;
// }
// 2
// char buf[1024];
// while (ifile.getline(buf, sizeof(buf)))
// {
// cout << buf << endl;
// }
// 3
// string buf;
// while(getline(ifile, buf)){
// cout << buf <<endl;
// }
Person p;
ifile.read((char *)&p, sizeof(p));
cout << p.name_ << p.age_ << endl;
}
ifile.close();
}
int main()
{
test02();
return 0;
}
异常处理的构造和析构函数
若在 try 中抛出异常,在转到 catch 前,会对有关对象进行析构
#include <iostream>
using namespace std;
//异常处理的构造和析构函数
class Student{
private:
string name;
int sno;
public:
Student(string name1,int sno1)
{
name=name1;
sno=sno1;
}
~Student()
{
cout<<"Destruct Student:"<<sno<<endl;
}
void checkSno()
{
if(sno==0)
{
throw sno;
}
else
{
cout<<name<<":"<<sno<<endl;
}
}
};
int main()
{
try
{
Student a("pink",1);
a.checkSno();
Student b("floyd",0); //构造对象
b.checkSno(); //抛出异常跳到catch语句块中
}
catch(int)
{
cout<<"error:sno=0!"<<endl;
}
return 0;
}
可以看到上面这段代码的输出结果是先对a、b析构然后再转到catch输出内容。
类模板与继承
要注意继承的时候要加上基类的类型
#include <iostream>
using namespace std;
template<class T>
class Base
{
public:
T c;
};
template<class T>
class Deri: public Base<T>
{
public:
T a;
};
int main()
{
Deri<int> d;
d.a=4;
d.c=6;
cout <<d.Base::c<<endl;
// cout << sizeof(Deri<int>)<<endl;
return 0;
}
继承时不能被继承的函数
包括:构造函数、拷贝构造函数、析构函数,注意:operator=函数是可以被继承的
#include <iostream>
#include <cstring>
using namespace std;
class A{
public:
A(){
}
A(int price, int weight):price_(price), weight_(weight){
}
A& operator=(const A& a){
cout << "operator=1" << endl;
}
private:
int price_;
int weight_;
};
class B: public A{
public:
B(int price, int weight):price_(price), weight_(weight){
}
private:
int price_;
int weight_;
};
int main(){
// A a1(1,0);
// A a2;
// a2=a1;
B b1(10,0),b2(2,0);
b2=b1;
return 0;
}
上面说明operator=函数被继承下去了。
静态数据成员的只能在类外初始化
类内定义,类外初始化一次,然后在整个程序运行期间都可以存在
原因
1、静态成员变量存储在静态存储区域内,在编译阶段就为他们分配内存空间,
2、静态成员变量是类级别的,他在所有类实例中是公有的,如果允许在类内部初始化,就可能导致每个实例都有一个独立的副本
3、这样可以避免多次初始化
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <math.h>
using namespace std;
class Point
{
public:
Point(int x, int y) : x_(x), y_(y)
{
}
void setX(int val)
{
x_ = val;
}
void setY(int val)
{
y_ = val;
}
int getX() const
{
return x_;
}
int getY() const
{
return y_;
}
private:
int x_;
int y_;
};
class Circle
{
public:
Circle(int x, int y, int r) : center_(x, y), r_(r)
{
num_++;
cout << "Circle number: " << getNum() << endl;
;
}
static int getNum()
{
return num_;
}
friend double getDistance(const Circle &c1, const Circle &c2);
private:
static int num_;
Point center_;
int r_;
};
int Circle::num_ = 0;
double getDistance(const Circle &c1, const Circle &c2)
{
double x=double(c2.center_.getX()-c1.center_.getX());
double y=double(c2.center_.getY()-c1.center_.getY());
return sqrt(x*x + y*y);
}
void testCircle()
{
Circle c1(1, 2, 3);
Circle c2(4, 6, 2);
cout << getDistance(c1, c2) << endl;
}
int main()
{
testCircle();
return 0;
}