C语言的编程风格是面向过程式的程序设计。所谓过程式的设计语言就是当编写代码解决一个问题时,代码的编写方法是从上到下,逐步求精,一些公用的功能写成函数,需要用到结构体时就定义结构体。
C++本身支持C语言风格的过程式程序设计,同时也支持基于对象的程序设计和面向对象的程序设计。
1、基于对象和语言有关,而面向对象和程序有关。
2、基于对象仅仅支持了抽象数据类型的功能,只是建立了一个层次体系。而面向对象是根据对象的实际类型不同,可以自动完成不同的行为,而仅仅通过一致的调用形式。
3、面向对象主要有三个特性,封装、继承和多态,而基于对象是面向对象的初级阶段,只有封装。
基于对象和面向对象程序设计的主要区别是:在基于对象的程序设计中额外运用了继承性和多态性技术。从而实现易维护,易扩展,模块化
c中结构体,c++类,类比结构更强大的地方在于:在类中,不仅可以定义成员变量,还可以定义成员函数(也叫方法)以实现一些功能
(1)一个项目(工程)中可以包含多个.cpp文件和多个.h头文件,
一般.cpp叫源文件,.h叫头文件(函数的声明,一些类、结构的定义,一些#define等)
(2)C++的头文件拓展名一般以.h居多。此外,还有.hpp,.hpp一般来讲就是把定义和实现都包含在一个文件里,有一些公共开源库就是这样做,主要是能有效地减少编译次数。
(3)很多C++提供的头文件已经不带扩展名.h。以往在C语言中经常用到的头文件,如比较熟悉的stdio.h等,在C++98标准之后,都变成了以c开头并去掉拓展名的文件,如stdio.h就变成了cstdio文件。
c++属于编译型语言,编译型语言在执行前需要一个专门的编译过程,把程序编译成二进制文件(可执行文件),执行时直接执行就可以
解释型语言,在执行时先解释再执行。
可移植性,在不同的操作系统上,代码生成的可执行文件执行后能够实现相同的功能,那么这份源代码就被称为可移植的。
命名空间是为了防止名字冲突而引入的一种机制。防止在大型项目中多个.cpp存在函数名、类名和变量名同名的问题。
可以把命名空间看成一个作用域,这个命名空间里定义的函数与另外一个命名空间里定义的函数,即便同名,也互不影响。
namespace 命名空间名字
{
void radius()
{
// ......
}
} //这里无须分号结尾
两个冒号——>“作用域运算符”:
命名空间名::实体名
//MyProject.cpp源文件代码如下
namespace NMZhangSan //定义命名空间
{
void radius()
{
printf("NMZhangSan::radius函数被执行.\n");
}
}
int main() //主函数
{
NMZhangSan::radius(); //调用NMZhangSan命名空间下的radius函数
}
需要对那个命名空间建立一个.h的头文件指明函数有哪些
namespace NMLiSi
{
void radius(); //函数声明
}
然后将头文件包含进来再调用
#include "MyProject2.h"
否则无法通过编译
C++中输入/输出用的标准库是iostream库(输人/输出流)。流就是一个字符序列。那怎样在程序中使用这个标准库呢?只需要包含一个头文件就可以了
#include < iostream >
1、输入输出都有个缓冲区,不能每次都仅输出一个字符,由于要调用内核
2、<<有左结合和右结合,不同编译器不一样
3、std::endl函数模板名,相当于函数指针,用于换行和刷新输出缓冲区(flush)
4、c中scanf需要地址符号&,cin不需要地址符号&,因为引用
5、>>输入运算符,类成员函数重载多个版本,处理不同数据类型
c++需要时随时定义变量,c只在函数头部定义变量。
新标准可以使用{} ,表示一个数
int abc {3};
int abc = {3}; 两种方式都可以
int abc[]={11,12,13};
这样写可以统一起来
变量的自动类型推断,声明变量时根据变量初始值的类型自动选择匹配类型,发生在编译期
同一个头文件被包含(include)多次
head.h 定义 int glo=5;
head2.h 定义 int glo2=3;然后#include "head.h"
myProgect.cpp
#include"head.h"
#include"head2.h"
那么head.h就被包含了两次,glo就被定义了两次就会出现重定义错误。
C/C++中有两种宏实现方式避免这种情况:#ifndef方式和#pragma once方式
定义的名字不能一样
#ifndef __HEAD__
#define __HEAD__
//......
#endif
#pragmaonce
//......
ifndef会使得编译时间相对较长,可能出现后面的名字即宏名撞车,pragma once不支持跨平台
所以ifndef由语言支持所以移植性好,pragma once可以避免名字冲突
引用是未变量起别名,与原变量名占用同一块内存,并不额外占用内存,必须定义时初始化,类型必须一样,不能绑到常量上。
int value = 10;
int &refval = value;//引用必须绑定到变量或对象上,不能绑到常量上
void changeValue(int &v) {
v = 22;
}
常量就是不变的量。可以有几个关键字修饰 const关键字、constexpr关键字
const添加上,该值就不可以改变
const int var = 17; //承诺值不变,实际可改变
int &var2 = (int &)var; //const int需类型转换
var2 = 5; //var2修改值后会重新开辟内存空间?
cout <<var <<endl; //17
cout <<var2 <<endl; //5,引用不是别名这么简单
通过引用可以修改值,但是输出还是那个const值
int func() {return 3;}
int v1 = 12;
const int v2 = v1 + func(12); //运行时求值也行
constexpr表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。编译时求值,能够提升运行时的效率,有利于系统优化等。
声明为constexpr的变量一定是一个const变量,而且必须用常量表达式初始化:
constexpr int mf = 20; //20是常量表达式
constexpr int limit = mf + 1; // mf本身是常量,因此mf + 1是常量表达式
int limit2 = mf + 1; // 可以
int mf2=20;
constexpr int limit = mf2 + 1; // 不可以,mf2是变量
constexpr int sz = size(mf2); //mf2 是变量,不可以
constexpr int sz = size(mf); //mf是常量,只有当size是一个constexpr函数时才是一条正确的声明语句,比如下面
constexpr int size(int abc) { //constexpr函数中代码要尽可能简单,变量定义时要初始化
int a3 = 5;
//int var; //这样不进行初始化就会出错,所有的都要在定义时初始化
//printf("good\n");//这样错误,constexpr函数中需要调用constexpr函数
return abc * a3;
}
只要是constexpr修饰的,所有的都必须是常量,不能有变量混入,使用函数时 size(int a),传入的也是常量。
constexpr 和const在使用引用修改值时发现并没有改成功
在constexpr声明中如果定义了一个指针,限定符conxtexpr仅对指针有效,与指针所指的对象无关。
const int*p = nullptr; //p是一个指向整形常量的指针
constexpr int* q = nullptr; //q是一个指向整数的常量指针
范围for语句,遍历序列,支持begin和end成员函数返回迭代器的容器就可以支持范围for语句。
使用引用可以避免复制提高效率。
int v[]{12, 13, 14, 16, 48};
for(auto x : v) //依次复制v数组中元素值给x
cout <<x <<endl;
for(auto x : {12, 13, 14, 16, 48}) //依次复制元素序列中值给x
cout <<x <<endl;
for(auto &x : v) //引用避免复制
cout <<x <<endl;
分为程序代码区、静态存储区、动态存储区。
动态存储区存放函数形参,局部变量,函数调用时调用现场的一些数据和返回地址。这些数据在函数调用时开始分配空间,调用完毕后就会被回收,这种分配和释放被认为是动态的。
希望函数中局部变量的值在函数调用后不会被释放,在下一次调用时还有上一次的值,这种局部变量称为局部静态变量,用static关键字
栈,函数局部变量,编译器自动分配释放。栈空间有限,系统规定,分配快。
堆,程序员malloc/new申请,及时free/delete释放(节省系统资源,防止耗尽导致程序崩溃,忘记释放程序结束后操作系统回收)。堆空间由程序员自由分配,大小理论上不超过实际物理内存,分配慢。
全局/静态存储区,放置全局变量和静态变量,程序结束时释放。
常量存储区,存放常量,字符串常量等,不允许修改。
程序代码区,存放代码
void * malloc(int NumBytes);
返回值是void *,void *类型可以强制转换成任何其他类型得指针,如果分配成功则分配指向该分配空间的指针,失败则返回空指针NULL
void free(void *Ptr);
例子
int * p= NULL;
p= (int *)malloc(10 * siezof(int));//返回void *指针后转换成int *类型的指针
free(p);
运算符,不是函数。比malloc和free做了额外的初始化和清理工作。
指针变量名 = new 类型标识符; //未初始化
指针变量名 = new 类型标识符(); //默认值初始化
指针变量名 = new 类型标识符(初始值); //指定值初始化
指针变量名 = new 类型标识符[内存单元个数]; //数组,delete[]释放
int *p = new int[100];
if(p != NULL) {
int *q = p;
*q++ = 1;
*q++ = 5;
delete[] p;//delete[]回收整个数组,[]内写了数字无意义,会被忽略
//delete p只会回收第一个数组元素空间
p = NULL;
}
new时用了[],delete就要用[],[]里面不用写数组元素个数,写了也会忽略。
nullptr代表空指针,与指针相关尽量使用nullptr,不使用NULL。
nullptr能够避免在整数和指针间发生混淆。
cout <<type(NULL).name() <<endl; //输出int
cout <<type(nullptr).name() <<endl; //输出std::nullptr_t
因此两者类型不一样
如果将两者当函数实参传递到函数中会因为类型不同而调用不同的重载函数
(1)对于指针的初始化,能用nullptr的全部用nullptr。
(2以往用到的与指针有关的NULL的场合,能用nullptr取代的全部用nullptr取代。
即关于指针的null全用nullptr
成员函数 stu.number = 1001;
指针 std->number=1001;
struct student {
public:
int number;
char name[10];
void func() {
number++;
}
}
public,共有/公共。修饰的成员(成员变量、成员函数),能被外界访问,类似外部接口。
private,私有,修饰的成员(成员变量、成员函数),只能被内部定义的成员函数使用。
struct(继承也)默认public,class(继承也)默认private。
用户自定义是数据类型(关联了操作),定义的类变量称为对象,一块能存储数据并具有某种类型的内存空间。
1、结构的默认访问类型是public,而类的默认访问类型为private。
2、结构的继承默认是public,而类的默认继承是private。
如果明确写出public、protected、private那么这两个没有什么区别,区别主要是在缺省访问级别时有所不同
类定义放在h头文件,类实现放在cpp源文件,使用时导入h头文件。头文件的文件名可以和类名相同。.cpp也可以相同
前置返回类型,函数形参不使用时可以无形参名,函数调用是必须传递实参值。
后置返回类型,函数声明或定义中将返回类型写在参数列表之后,用于复杂返回类型和特殊场合。
auto表示函数返回类型放到参数列表之后,返回类型是通过—>开始的
auto func(int, int) -> int; //声明
auto func(int a, int) b -> int { //定义
return 1;
}
这样做主要是为了模板方便
template<class T1,class T2>
auto getValue(T1 v1,T2 v2)->decltype (v1 + v2)
{
return v1 + v2;
}
返回的类型由传入的参数确定
inline int func(int a) { //函数增加inline,内联函数
return a*a+3;
}
函数体很小,调用又很频繁的函数要频繁地进行压栈、出栈动作以处理函数调用和返回的问题,要频繁地开辟内存,耗费系统性能。
使用内联函数的注意点
1、把inline函数的定义放到头文件中,在每个调用该inline函数的文件中包含该头文件。这种方法保证对每个inline函数只有一个定义,且程序员无需复制代码,并且不可能在程序的生命期中引起无意的不匹配的事情。
用函数本体取代函数调用,显然可以增加效率。但同时带来的问题是函数代码膨胀,所以内联函数函数体要尽可能短小,这样引人inline才有意义。调用一个函数时需要压栈开辟内存等动作,假如这些动作需要花费1s的时间,如果在这个函数中代码的执行需要花费1000s的时间,那这个函数写成内联函数之后,也就节省了1s的时间,但是源文件代码却膨胀的很大。如果在多个地方调用这个函数,那就相当于多个地方出现代码的重复膨胀,代码在程序运行时也是要占用内存的,因为内存中有代码段专门保存程序代码。
constexpr就是一种内联函数,内联函数比较像宏展开
1、define是 预处理时处理的宏; 只进行简单的字符替换,无类型检测,而inline是编译时的内联函数,有类型检查,且编译器有权拒绝内联。
2、inline有传参,这点和普通函数一致,define只是简单的文本替换
1、void表示无返回类型或无输入参数
void func1(void)
{ }
2、函数返回指针和返回引用
int *myfunc1() {
int tmp = 9; //tmp应该成为全局变量
return &tmp;//局部变量有隐患
}
int &myfunc2() {
int tmp = 9;//tmp应该成为全局变量
cout <<&tmp <<endl;
return tmp; //局部变量有隐患
}
int &k = myfunc2();
cout <<&k<<endl; //调用打印结果一样
int k = myfunc2();
cout <<&k<<endl;
下面这个不成立,const关键字在比较同名函数时会被忽略掉
//函数重载不成立
void fs(const int i){}
void fs(int i){}
1、常量指针 ——防止使用指针来修改指向的值
int age=30;
const int * p=&age;
2、指针常量——防止改变指针指向的位置
int * const p=&age;
其他注意的地方
const char * const p或char const * const p
指向变量和指向内容都固定
char const *p 等价 const char *p 还是就近
就近,看是给指针还是指针后面的元素,
给了指针就不能通过指针修改对应的值,可以通过其他方法修改。
给了后面的元素则后面的元素不能修改也就是不能更换指的地址,但可以改变地址上的内容
int i = 100;
const int &a = i; //不能通过引用变量修改值
i = 200; //正确
a = 200; //错误
const int &b = 156;//正确,字面值初始化常量引用
int &b = 156;//错误
b = 200; //错误,b看作常量,不能修改值
上面&a可以认为是一个词,就是a是i的别名,那么加上const,b就不能动,但是是对b加的,对i没有影响,就像下面一样不想在局部函数里修改值,但是原值可以修改。
引用但不想改变值在前面加上const
void fs(const student &stu) { //可接受const引用,普通引用,字面值常量
//stu.num = 1010;//const引用不能修改
}
普通的student不能接受const的student,但是const student&可以接受普通的student,也可以就收const类型的,这样既可以接受普通引用也可以接受常量就更加灵活
STL教程5-STL基本概念及String和vector使用
s.c_str(); 返回字符串里的内容,返回的是一个指向正规C字符串的常量指针,所以以"\0"结尾
注意:
1、vector不能用来装引用,引用只是一个别名,不是一个对象,所以下面会报错误
vector< int& >abc;//语法错误
2、for语句中,不要改变vector的容量,增加、删除元素都不可以。这样会发生混乱
vector<int> vecvalue{ 1, 2, 3, 4, 5 };
for(auto vecitem : vecvalue) {
vecvalue.push_back(888);//错误
cout <<vecitem <<endl;
}
由于不同容器的实现不一样,插入元素可能会导致内存发生变化,那么迭代器就会失效
1、const_iterator迭代器,如果容器对象是一个常量,就必须使用这个迭代器,这个迭代器只能读。
2、不管是否是常量容器,cbegin和cend,这两个返回的都是常量迭代器const_iterator,如下面所示,不能* iter修改其元素。
系统自动进行,无需人为介入的类型转换
int m = 3+45.6
c语言风格的强制类型转换——将类型用()括起来
int k = 5 % (int)3.2;
k = 5 % int(3.2);//函数风格
STL教程3-类型转换static_cast、dynamic_cast、const_cast、reinterpret_cast方法
1、相关类型转换,比如int转换为double
2、有继承关系的子类转换为父类
3、void*和其他指针的互转
4、不能用于其他类型的指针转换
用于父类转换为子类
去除指针或者引用(只能去这俩,不能其他的)的const属性
去掉以后不要重新赋值,赋值的话是未定义行为,不要这么做以免产生无法预料的后果。
这个可以去掉表达式的常量属性
可以处理无关类型的转换,随便转都行。
常用于
1、将整型转换成指针,一种类型指针转换成另一种
2、也可以一个指针到一个整型