Cpp 基础学习
第二章 变量和基本类型
复合数据类型
引用
引用也即给对象起别名
int i=0;
// ri为i的引用
int &ri=i;
引用注意事项
// 引用必须时对一个对象的引用
int &ri=10; //错误
// 引用必须为同一种类型
double pi=3.14;
int &rpi=pi; //错误
// 引用必须要有初始化
int &ra; //错误,为初始化
指针
类型指针
指针本身是一个对象,它的值为一个对象的地址,它可以赋值与拷贝。因为是一个对象因此与引用不同,它可以不初始化,指针的定义为“类型 *变量名”,例如
int a=0;
// 定义一个变量pa,其类型为int*也即整形指针,其值为a的地址
int *pa=&a; //&a表示取出a的地址
由上可值pa类型为int* 但一般编程习惯是把*号紧邻变量名左边,因为这样容易产生误解,例如在定义多个变量时,事实上int*仅作用于一个变量,而非全部,如:
int* a,b,c;
int *a,b,c;
上面程序第一条语句很多人也许会误以为a,b,c均为int指针,实则不然仅a为int指针;b,c为int类型而非指针。因此通常做法是把*号紧挨于变量左边就像第二条语句一样,就不容易产生误解了。
利用指针访问对象要使用到 “*”解引用操作符
int ival=2;
int *pival=&ival;
cout<<*pival<<endl;
void* 指针
void* 指针是一种特殊类型的指针,可以存放任意对象的地址。以下程序是正确的
double obj1=3.14;
void* p=&obj1;
int obj2=2;
p=&obj2;
因为不知道其具体类型是什么,因此不能直接使用解引用符号访问该指针指向的对象。
const限定符
处理类型
auto类型说明符
通常我们不可能清楚知道每一个表达式的值的类型,auto关键字用于让编译器自己推导表达式类型,例如:
auto a=2;
auto b=3.14;
这是编译器根据2和3.14能推导出a的类型为int,b的类型为double。 auto也能在一条语句同时定义多个变量,但必须要有同一类型,如:
auto a=0,b=3.14; //错误,0和3.14类型不同
auto c=0,d=4; // 正确
auto e=3.24,f=3.14; //正确
decltype类型指示符
其作用是返回操作数的数据类型。在次过程中编译器会分析表达式并得到其类型,却不会计算表达式的值,如:
decltype(f()) a=b;
编译器会分析并得到f()函数的返回值类型,但并不会调用f()函数。 表达式也可以是一个变量或者一个具体的值
const int ci=0,&cj=ci;
decltype(ci) x=0; // x类型为const int
decltype(cj) y=x; // y是const int&类型
decltype(cj) z; // z为引用类型必须初始化
注意事项
int i=42,*P=&i,&r=i;
decltype(r) z; //错误 z为引用类型必须初始化
decltype(r+0) x; //正确,x为int类型
decltype(*p) c; //错误,c为引用类型必须初始化
decltype((i)) n; //错误,n为引用类型必须初始化
decltype(i) m; // 正确,m为int类型
上述代码中r为引用类型这是显然的,而r+0很明显是一个具体的int类型的值因此是int类型。 值得注意的是,如果表达式为解引用操作,则decltype得到的是引用类型。如上decltype(*p)得到的是引用类型int&,而非int。之所以会这样是因为解引用我们得到的是实实在在的对象,而非数值,我们对解引用后的p进行操作会修改i的值。再者就是变量名加括号和不加括号有时候会得到两种不同类型,如上。
第三章 字符串,向量和数组
标准库类型vector
vector表示对象的集合,里面存放的对象的类型相同,所以vector也常常被称作“容器”。 vector是C++中的一个类模板而非类,对于类模板来说只有提供了其他额外信息才能实例化为一个具体的类,以vector为例,vector本身不是类,但在其后面加上一对尖括号里面存放某个类型就成了一个类,比如
vector<int> ivec; //ivec保存int类型对象
vector<vector<int>> c; // c保存vector<int>类型
值得注意的是vector虽然能够容纳大部分对象作为其元素,但因为引用不是对象,所以不存在包含引用的vector
迭代器介绍
数组
第四章 表达式
第五章 语句
第六章 函数
函数基础
局部对象
局部静态对象
有些时候需要令局部变量的生命周期贯穿函数调用及之后的时间,可以将局部变量定义位static类型获得这一对象,成称为局部静态对象。局部静态对象仅在第一次执行时进行初始化,直到程序结束后才被销毁。期间除第一次初始化外,就算多次执行函数也不会产生影响,例如:
int count_call(){
static int ctr=0;
return ++ctr;
}
int main(){
for(int i=0;i<10;++i){
count_call();
}
return 0;
}
以上代码可以计算函数调用次数,在第一次调用函数时对静态对象ctr进行初始化,之后ctr便一直存在函数count_call的作用域中,期间第二次,第三次...后的"static int ctr=0;"这条语句都不会对程序产生影响,因为静态对象只初始化一次,但“++ctr"不是初始化语句且ctr会一直在作用域中所以仍然有效。知道程序结束才会将ctr对象销毁。
传递参数
特殊通途语言特性
内联函数和constexpr函数
内联函数一般比较适用于较小的函数,定义内联函数可以避免函数调用的开销,从而提高运行效率,因为内联函数通常是在其每个调用点内联的展开,有点类似于宏,比如以下代码
inline int test(int a,int b){
return a>b?a:b;
}
int main(){
cout<<test(3,4)<<endl;
return 0;
}
"cout<<test(3,4)<<endl;"这条语句在编译过程中因为test为内联函数所以会在其调用点内联展开为以下形式 “cout<<3>4?3:4<<endl”
constexpr函数指用于常量表达式的函数,其定义方法与普通函数类似,不过要遵循几条约定:函数返回值类型和形参类型都必须为字面值类型,而且函数体中必须有且仅有一条return语句。
函数指针
函数指针指向函数而非对象,和其他指针一样,函数指针指向某种特定的函数类型,函数类型由它的返回值和形参类型共同决定而与函数名无关。例如:
//比较两个数大小
bool compare(int a,int b){
return a>b;
}
此函数类型为"bool compare(int,int)",若想定义一个指向该函数的指针,用指针替换函数名即可,例如
bool (*pf)(int,int); //未初始化
关于函数指针的复制和初始化,当使用函数名作为值时,该函数会自动转换为指针,例如
pf=compare;
pf=&compare;
在compare为一个函数名时以上两条语句是等价的‘&’可写可不写
函数指针形参
第七章 类
访问控制与封装
友元
将非成员函数作为友元
类可以允许其他类或者函数访问其私有成员,方法是令其他类或者函数定义为它的友元(friend),把函数作为友元的方法是增加一条以friend关键字开头的函数声明即可,例如
class test{
friend int compare(const test&,const test&);
private:
int a;
int b;
};
int compare(const test&,const test&){
....函数体...
}
以上代码中如果不加‘friend int compare(const test&,const test&);‘这条语句,显然compare函数不属于test类,则它无法访问test私有成员,如果加上的话那么compare函数就成了test的友元,就能够访问其私有成员了。
将其他类作为友元
将other类作为test类的友元,other类将具有访问test私有成员的能力
class test{
friend class other;
}
将其他类中某个成员函数作为友元
将other类中某个成员函数作为友元,该函数将具有访问test私有成员的能力,但该类的其他函数不行。
class test{
friend void test::compare(int a,int b);
}
类的其他特性
类的作用域
构造函数再探
委托构造函数
委托构造函数是指使用该类其他构造函数执行自己初始化过程的函数,在委托函数内成员初始列表仅有一个入口就是类名本身。例如:
class Sale_data{
public:
// 非委托构造函数
Sale_data(char c,int b,double b):bookNo(b),a(c),price(b){}
// 委托构造函数使用非委托构造函数初始化
Sale_data():Sale_data('',0,0.0){}
Sale_data(char c):Sale_data(c,0,0.0){}
};
第八章 IO库
第九章 顺序容器
第十章 泛型算法
第十二章 动态内存
动态内存与智能指针
C+动态内存管理可以通过new和delete来完成,不过这种方式极其难以管理因此C11引入了智能指针shared_ptr,unique_ptr,weak_ptr。三种指针均在memory头文件中。
shared_ptr类
shared_ptr类通vector一样也是模板,因为我们也需要知道一个指针到底指向的是什么类型,
shared_ptr<string> p1;
shared_ptr<vector<int>> p2;
如上所示定义了一个指向string和指向vector<int>类型学的指针
make_shared函数
第十三章 拷贝控制
拷贝,赋值与销毁
拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则该该构造函数为拷贝构造函数。
class test{
public:
test(const test&);
private:
int no;
string name;
};
test::test(const test& orig):no(orig.no),name(orig.name){}
直接初始化和拷贝初始化
直接初始化是指要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。而拷贝初始化是将右侧运算对象拷贝到正在创建的对象中,需要的话还要进行类型转换。
string dots(10,'.'); //直接初始化
string s(dots); //直接初始化
string s2=dots; //拷贝初始化
string s3="fdafa" //拷贝初始化
拷贝初始化发生情况
使用‘=‘复制时
将一个对象作为实参传给非引用类型的形参
void test(int a){ } int main(){ int a=9; test(a); //这里就发生了拷贝初始化 }
从一个返回值类型为非引用类型返回一个对象
int test(){ int a=9; return a; //发生拷贝初始化 }
使用花括号初始化数组的元素或聚合类成员
析构函数
当一个对象被销毁时会自动调用析构函数,析构函数由 ~类名 定义,如
class Test{
public:
~Test(){} // 析构函数
};
阻止拷贝
通常来说大多数情况不会用到阻止拷贝,但也有特例如在iostream中就应该阻止拷贝以避免多个对象读取相同的io. 在新标准下通常可以将拷贝函数和拷贝赋值函数定义为删除的函数以阻止拷贝赋值
class NoCopy{
public:
NoCopy(const NoCopy&) = delete; //阻止拷贝
NoCopy &operator=(const NoCopy&) = delete; //阻止赋值
}
在新标准前一般是将拷贝函数定义为private以阻止拷贝
第十四章 重载运算符与类型转换
第十五章 面向对象程序设计
OOP概述
继承
通过继承联系在一起的类构成一种层次关系。通常在层次的根部有一个基类,其他所有类都直接或间接从基类继承而来,这些类称为派生类。 在C++中对于某些函数基类希望派生类都实现自己适合的版本,此时就将基类这些函数定义为虚函数。也就是说如果子类必须对父类的虚函数进行重写。这也是虚函数与普通函数的区别,普通函数若子类不重写则会使用父类的版本,而虚函数必须重写。(类似与java中@override注释)
动态绑定(多态)
通过动态绑定我们可以使一段代码既可以处理父类对象也可以处理子类对象,例如
class Fruit{
};
class Apple:Fruit{
};
class Oringe:Fruit{
};
void test(const Fruit &a){
}
在上段代码中Apple和Oringe都继承自Fruit,所以在test中以Fruit对象为参数的函数Fruit,Apple,Oringe对象均可作为其参数。
访问控制
前面已经学习了两种访问控制关键字:public,private。其中public可以允许可他对象访问自己成员,而private则不允许其他对象访问自己成员包括子类,那么如果我们希望子类能够访问其成员而其他类对象不能访问该如何做,这是就要用到protected关键字了。
抽象基类
Subscribe to my newsletter
Read articles from 暮秋 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by