C++学习

指针与const

 

const char * a; //指向const对象的指针或者说指向常量的指针。 char const * a; //同上 char * const a; //指向类型对象的const指针。或者说常指针、const指针。 const char * const a; //指向const对象的const指针。

如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。

所以 在左侧,const是修饰指针所指向的常量,也就是指向的地址是常量,也就是地址不能变化。

const在右侧 是修饰指针,指针常量:指针本身不可变化,但是指向的内容可以变化

指针常量

 

int * const p //指针常量 在这个例子下定义以下代码: int a,b; int * const p=&a //指针常量 //那么分为一下两种操作 *p=9;//操作成功 修改的是指针指向的内容 p=&b;//操作错误 修改的是指针变量

常量指针

常量指针理解为常量的指针,const在*左边;

 1.常量指针指向的对象不能通过这个指针来修改,可是仍然可以通过原来的声明修改;   2.常量指针可以被赋值为变量的地址,之所以叫常量指针,是限制了通过这个指针修改变量的值;   3.指针还可以指向别处,因为指针本身只是个变量,可以指向任意地址;

 

int const * p //常量指针 在这个例子下定义以下代码: int a,b; int const *p=&a //指针常量 //那么分为一下两种操作 *p=9;//操作错误 修改的是指针指向的内容 p=&b;//操作成功 修改的是指针变量

指向常量的指针常量该怎么写? 答案:const int * const b = &a;//指向常量的指针常量

函数参数中用到的const

 

void StringCopy(char *dst, const char *src);

其中src 是输入参数,dst 是输出参数。给src加上const修饰后,如果函数体内的语句试图改动src的内容,编译器将指出错误。这就是加了const的作用之一。

参数为什么采用引用的形式?

 

void func(const A &a)

为了防止篡改与提升效率;

对于非内部数据类型的参数而言,象void func(A a) 这样声明的函数注定效率比较低。因为函数体内将产生A 类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间

为了提高效率,可以将函数声明改为void func(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数void func(A &a) 存在一个缺点:

“引用传递”有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const修饰即可,因此函数最终成为

 

void func(const A &a)。

以此类推,是否应将void func(int x) 改写为void func(const int &x),以便提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。

const关字进行说明的成员函数,称为常成员函数。只有常成员函数才有资格操作常量或常对象,没有使用const关键字明的成员函数不能用来操作常对象。

 

class Apple { private: int people[100]; public: Apple(int i); const int apple_number; }; Apple::Apple(int i):apple_number(i) { }

const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.

在C++中,static静态成员变量不能在类的内部初始化。在类的内部只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化。

Static

Static 标识的变量,生命周期内仅分配一次内存。

这对于在C / C ++或需要存储先前函数状态的任何其他应用程序非常有用。函数内的成员变量变成了类的成员变量;

类中的静态变量也是只分配一次内存,因此不同的类对象共享该成员变量;

 

#include<iostream> using namespace std; class Apple { public: static int i; Apple() { // Do nothing }; }; int Apple::i = 1; int main() { Apple obj; // prints value of i cout << obj.i; }

注意 类的静态变量访问方式是通过::形式

范围解析运算符 ::类的静态函数 也通过类名加::fun()

this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。

this指针的使用:

(1)在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this。

(2)当参数与成员变量名相同时,如this->n = n (不能写成n = n)。这个是常见的使用方式。

其实this一直存在与非静态成员函数中,作为一个隐含形参。

类的成员默认是private,而结构是public 这是类和结构体的唯一区别;

strcpy:

 

char *strcpy(char *dest, const char *src)

把src指向的字符串赋值给dest

 

Person& add_age(int a){ age+=a; return *this; } Person p("zhangsan",20,Person::BOY); cout<<p.get_age()<<endl; cout<<p.add_age(10).get_age()<<endl;

add_age()方法 返回是Person的类地址;

那么通过this 前增加* 实现指针的解引用,指针的解引用变成了指针指向的地址的内容变量;

断点看下 this的实际类型:

this 就是 Persion类型的指针常量。因此可以修改指针指向的内容对象;

inline

内联函数放在函数的定义前面,而不是声明前面。

内联函数能提高函数的调用效率,以提升代码膨胀为代价的。

内联函数使用与函数体比较小的函数;

虚函数可以是内联函数吗?

虚函数可以是内联函数,但是当虚函数表现为多态时,不能定义为内联函数

 

#include <iostream> using namespace std; class Base { public: inline virtual void who() { cout << "I am Base\n"; } virtual ~Base() {} }; class Derived : public Base { public: inline void who() // 不写inline时隐式内联 { cout << "I am Derived\n"; } }; int main() { // 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。 Base b; b.who(); // 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。 Base *ptr = new Derived(); ptr->who(); // 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。 delete ptr; ptr = nullptr; system("pause"); return 0; }

多态: 指针调用呈现多态性,多态是指在运行期间才能确定的类型。

C++多态的两个条件:

1、调用函数的对象必须是指针或者引用;

2、被调用的函数必须是虚函数,且虚函数完成了重写;

因为基类指针可能指向派生类,当delete的时候,如果不定为虚函数,系统会直接调用基类的析构函数,这个时候派生类就有一部分没有被释放,就会造成可怕的内存泄漏问题。

虚函数重写:

派生类中有一个与基类完全一样的函数,之类的虚函数重写包括函数 名称、参数、返回值完全一样;

协变:重写的虚函数返回值不同;

若定义为虚函数构成多态,那么就会先调用派生类的析构函数然后派生类的析构函数会自动调用基类的析构函数,这个结果满足我们的本意。

尽量把基类的析构函数定义为虚函数,这样继承下去的派生类的析构函数也会被变成虚函数构成多态

纯虚函数:

虚函数的后面写上=0;

包含纯虚函数的类叫做抽象类,或者接口类,不能被实例化;类似与JAVA中的接口或者抽象类不能被实例话;

个纯虚函数的作用就是强迫我们重写虚函数,构成多态。这样更加体现出了接口继承。

函数后面跟上overried 标识函数是否被重写;

class A final()标识函数被能被继承;

void fun final() 标识函数不能被重写;

Size of

空类的大小为一个字节;

一个类中 虚函数本身,成员函数(静态和非静态)和静态数据成员都不占用类对象的内存空间。

对于包含虚函数的类,无论多少个虚函数,只有一个虚指针vptr的大小;

C++中的vptr指针;

若类中包含虚函数,则在实例化时,增加虚函数指针vptr用来指引虚函数映射表。这样在父函数的指针调用虚函数时,会根据指针的对象是父类还是子类来决定调用那个函数实现。

基类的虚函数大小,在32位上是4个字节,在64位的系统上是8个字节;

 

class C : virtual public A, virtual public B { public: virtual void fun3() {} };

虚继承:

派生类虚继承多个虚函数,会继承多个虚函数的vptr;

纯虚函数:没有函数体的虚函数;

包含纯虚函数的类是 抽象类;

抽象类只能作为基类来派生新类使用;

一个抽象类的派生类,需要实现抽象类的所有纯虚函数;

抽象类可以有构造函数,并且构造函数可以是不是虚函数,但是析构函数可以是虚函数

虚函数的vptr与vtable

构造函数不可以为虚函数,并且除了 inline和explicit以外 不要有任何其他的修饰;

explicit显示标识 只有一个参数的类构造函数

implicit隐士标识 默认就是隐士的;

C++ explicit关键字详解_天空的博客-CSDN博客_explicit

 

class CxString // 使用关键字explicit的类声明, 显示转换 { public: char *_pstr; int _size; explicit CxString(int size) { _size = size; // 代码同上, 省略... } CxString(const char *p) { // 代码同上, 省略... } }; // 下面是调用: CxString string1(24); // 这样是OK的 CxString string2 = 10; // 这样是不行的, 因为explicit关键字取消了隐式转换 CxString string3; // 这样是不行的, 因为没有默认构造函数 CxString string4("aaaa"); // 这样是OK的 CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p) CxString string6 = 'c'; // 这样是不行的, 其实调用的是CxString(int size), 且size等于'c'的ascii码, 但explicit关键字取消了隐式转换 string1 = 2; // 这样也是不行的, 因为取消了隐式转换 string2 = 3; // 这样也是不行的, 因为取消了隐式转换 string3 = string1; // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载

explicit的作用就是防止类构造函数的隐士自动转化转换;

CxString string2 = 10;

explicit 只对一个参数的构造函数有用,因为多个参数的构造函数不会产生隐士的自动化转换。

如果除了第一个参数以外,其他参数都是默认值值的时候,explicit依然有效;

构造函数不能是虚函数;

 

struct bit_field_name { type member_name : width; };

 

struct _PRCODE { unsigned int code1: 2; unsigned int cdde2: 2; unsigned int code3: 8; }; struct _PRCODE prcode;

赋值时要注意值的大小不能超过位域成员的容量,例如 prcode.code3 为 8 Bits 的位域成员,其容量为 2^8 = 256,即赋值范围应为 [0,255]。

锁:std::unique_ptr<>

externa "C"

用来修饰函数;

C++编译生成的函数符号与C编译生成的符号不同,C++支持函数重载,因此编译生成的函数包含参数信息,而C没有;

例如int add(int a, int b)函数经过C++编译器生成.o文件后,add会变成形如add_int_int之类的, 而C的话则会是形如_add, 就是说:相同的函数,在C和C++中,编译后生成的符号不同。

如果C++中使用C语言实现的函数,在编译链接的时候,会出错,提示找不到对应的符号。此时extern "C"就起作用了:告诉链接器去寻找_add这类的C语言符号,而不是经过C++修饰的符号。

告诉连接器去找_add这类的C语言符号,而不是C++修饰的符号;

因此C++调用C中的函数:

 

//xx.h extern int add(...) //xx.c int add(){ } //xx.cpp extern "C" { #include "xx.h" }

在C中调用C++的函数:

 

//xx.h extern "C"{ int add(); } //xx.cpp int add(){ } //xx.c extern int add(); //通过extern声明下函数

编译的顺序:

 

gcc -c add.c // 先通过gcc 生成中间文件.o文件 g++ add.cpp add.o -o main // 在执行 g++ 命令

C++调用C函数的例子: 引用C的头文件时,需要加extern "C"

https://light-city.club/sc/basic_content/extern/

Struct

C中的struct只是单纯的数据集合,所以一般不包含函数。

C的结构体中不能使用C++的访问符 public或者private 和protected等

C++的结构体可以定义数据和函数;

C++的结构体可以设置访问修饰符;

Struct与clas的区别:

struct默认是public的 而class默认是private的

友元

友元提供了一种成员函数或者普通函数访问另外一个类私有的或者保护的成员的机制;

提高了程序的效率,但是破坏了类的封装性;

友元函数: 通过类的对象名访问类的私有或者保护成员;

 

#include <iostream> using namespace std; class A { public: A(int _a):a(_a){}; friend int geta(A &ca); ///< 友元函数 private: int a; }; int geta(A &ca) { return ca.a; } int main() { A a(3); cout<<geta(a)<<endl; return 0; }

友元类:声明在类中,实现在类外;

 

#include <iostream> using namespace std; class A { public: A(int _a):a(_a){}; friend class B; // 在A类的类中声明; private: int a; }; class B { public: int getb(A ca) { return ca.a; }; }; int main() { A a(3); B b; cout<<b.getb(a)<<endl; return 0; }

注意:友元关系没有继承性,没有传递性;

Using的操作

 

#include <iostream> #define isNs1 1 //#define isGlobal 2 using namespace std; void func() { cout<<"::func"<<endl; } namespace ns1 { void func() { cout<<"ns1::func"<<endl; } } namespace ns2 { #ifdef isNs1 using ns1::func; /// ns1中的函数 #elif isGlobal using ::func; /// 全局中的函数 #else void func() { cout<<"other::func"<<endl; } #endif } int main() { /** * 这就是为什么在c++中使用了cmath而不是math.h头文件 */ ns2::func(); // 会根据当前环境定义宏的不同来调用不同命名空间下的func()函数 return 0; }

 

class Base{ public: std::size_t size() const { return n; } protected: std::size_t n; }; class Derived : private Base { public: using Base::size; protected: using Base::n; };

改变访问性;

全局作用符号::

  • 全局作用域符(::name):用于类型名称(类、类成员、成员函数、变量等)前,表示作用域为全局命名空间

  • 类作用域符(class::name):用于表示指定类型的作用域范围是具体某个类的

  • 命名空间作用域符(namespace::name):用于表示指定类型的作用域范围是具体某个命名空间的

enum的使用

枚举会隐士的转换为int;

但是作用域不受限,容易带来命名冲突问题;

增加命名空间:

 

namespace Color { enum Type { RED=15, YELLOW, BLUE }; };

这样之后就可以用 Color::Type c = Color::RED; 来定义新的枚举变量了。如果 using namespace Color 后,前缀还可以省去,使得代码简化

更有效的办法是给枚举增加一个 类或者结构体限定

 

struct Color1 { enum Type { RED=102, YELLOW, BLUE }; };

C++11 引入了枚举类;

 

/** * @brief C++11的枚举类 * 下面等价于enum class Color2:int */ enum class Color2 { RED=2, YELLOW, BLUE }; r2 c2 = Color2::RED; cout << static_cast<int>(c2) << endl; //必须转!

类中的枚举类型;

 

class Person{ public: typedef enum { BOY = 0, GIRL }SexType; }; //访问的时候通过,Person::BOY或者Person::GIRL来进行访问。

枚举常量不占用对象的存储空间;在编译期间被求值;

枚举常量的隐含类型为整形;

Decltype

 

int i = 4; decltype(i) a; //推导结果为int。a的类型为int。

decltype用来查询表达式的类型;

与using和typeof合用,用于定于数据类型

 

using size_t = decltype(sizeof(0));//sizeof(a)的返回值为size_t类型 using ptrdiff_t = decltype((int*)0 - (int*)0); using nullptr_t = decltype(nullptr); vector<int >vec; typedef decltype(vec.begin()) vectype; for (vectype i = vec.begin; i != vec.end(); i++) { //... }

有点类似auto一样;

泛型编程中结合auto用来追踪 函数的返回类型;

 

template <typename T> auto multiply(T x, T y)->decltype(x*y) { return x*y; }

 

int i = 4; int arr[5] = { 0 }; int *ptr = arr; struct S{ double d; }s ; void Overloaded(int); void Overloaded(char);//重载的函数 int && RvalRef(); const bool Func(int); //规则一:推导为其类型 decltype (arr) var1; //int 标记符表达式 decltype (ptr) var2;//int * 标记符表达式 decltype(s.d) var3;//doubel 成员访问表达式 //decltype(Overloaded) var4;//重载函数。编译错误。 //规则二:将亡值。推导为类型的右值引用。 decltype (RvalRef()) var5 = 1; //规则三:左值,推导为类型的引用。 decltype ((i))var6 = i; //int& decltype (true ? i : i) var7 = i; //int& 条件表达式返回左值。 decltype (++i) var8 = i; //int& ++i返回i的左值。 decltype(arr[5]) var9 = i;//int&. []操作返回左值 decltype(*ptr)var10 = i;//int& *操作返回左值 decltype("hello")var11 = "hello"; //const char(&)[9] 字符串字面常量为左值,且为const左值。 //规则四:以上都不是,则推导为本类型 decltype(1) var12;//const int decltype(Func(1)) var13=true;//const bool decltype(i++) var14 = i;//int i++返回右值

引用与指针:

引用和指针的区别:

引用必须初始化,且不能为空,

 

int a = 1; int b = 2; int &r = a; //初始化引用r指向变量a int *p = &a; //初始化指针p指向变量a p = &b; //指针p指向了变量b r = b; //引用r依然指向a,但a的值变成了b

C++的引用在减少了程序员自由度的同时提升了内存操作的安全性和语义的优美性;

所以C++中的引用只是C++对指针操作的一个“语法糖”,在底层实现时C++编译器实现这两种操作的方法完全相同。

宏:

分为几种:###\

当定义的宏不能用一行表达完整时,可以用”\”表示下一行继续此宏的定义。

注意 \ 前留空格。

 

#define MAX(a,b) ((a)>(b) ? (a) \ :(b)) int main() { int max_val = MAX(3,6); cout<<max_val<<endl; }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值